<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="阿里巴巴Java开发手册(泰山版), 博客 blog">
    <meta name="description" content="熊猫小二的博客  xmxe&#39;s blog">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <!-- 为了引用qq空间图床文件 -->
    <meta name="referrer" content="no-referrer">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    <title>阿里巴巴Java开发手册(泰山版) | 熊猫小二</title>
    <link rel="icon" type="image/x-icon, image/vnd.microsoft.icon" href="/blog/favicon.ico">
    <link rel="stylesheet" type="text/css" href="/blog/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/blog/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/blog/css/my.css">
    <link rel="stylesheet" type="text/css" href="/blog/css/loading.css">

    <script src="/blog/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 6.3.0"></head>



   
<style>
    body{
       background-image: url(/blog/medias/cover.jpg);
       background-repeat:no-repeat;
       background-size:cover;
       background-attachment:fixed;
    }
</style>



<body>
    
  <div id="loading-box">
    <div class="loading-left-bg"></div>
    <div class="loading-right-bg"></div>
    <div class="spinner-box">
      <div class="configure-border-1">
        <div class="configure-core"></div>
      </div>
      <div class="configure-border-2">
        <div class="configure-core"></div>
      </div>
      <div class="loading-word">加载中...</div>
    </div>
  </div>
  <!-- 页面加载动画 -->
  <script>
    $(document).ready(function () {
      // document.body.style.overflow = 'auto';
      document.getElementById('loading-box').classList.add("loaded")
    })
  </script>

    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/blog/" class="waves-effect waves-light">
                    
                        <img src="/blog/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">熊猫小二</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/blog/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="#" class="waves-effect waves-light">

      
      <i class="fas fa-book" style="zoom: 0.6;"></i>
      
      <span>归档</span>
      <i class="fas fa-chevron-down" aria-hidden="true" style="zoom: 0.6;"></i>
    </a>
    <ul class="sub-nav menus_item_child ">
      
      <li>
        <a href="/blog/archives">
          
          <i class="fas fa-archive" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>归档</span>
        </a>
      </li>
      
      <li>
        <a href="/blog/tags">
          
          <i class="fas fa-tags" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>标签</span>
        </a>
      </li>
      
      <li>
        <a href="/blog/categories">
          
          <i class="fas fa-bookmark" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>分类</span>
        </a>
      </li>
      
    </ul>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/blog/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友链</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="#" class="waves-effect waves-light">

      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
      <i class="fas fa-chevron-down" aria-hidden="true" style="zoom: 0.6;"></i>
    </a>
    <ul class="sub-nav menus_item_child ">
      
      <li>
        <a href="/blog/about">
          
          <i class="fas fa-star-of-david" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>主页</span>
        </a>
      </li>
      
      <li>
        <a href="/blog/gallery">
          
          <i class="fas fa-images" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>相册</span>
        </a>
      </li>
      
    </ul>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
  
    <li>
      <a class="waves-effect waves-light" onclick="switchNightMode()">
        <i id="sum-moon-icon" class="fas fa-sun" style="zoom:0.65;" title="切换主题"></i>
      </a>
    </li>
  
  
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
          <img src="/blog/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">熊猫小二</div>
        <div class="logo-desc">
            
            熊猫小二的博客  xmxe&#39;s blog
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/blog/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="javascript:;">
			
				<i class="fa-fw fas fa-book"></i>
			
			归档
			<span class="m-icon"><i class="fas fa-chevron-right"></i></span>
		</a>
            <ul  >
              
                <li>

                  <a href="/blog/archives " style="margin-left:75px">
				  
				   <i class="fa fas fa-archive" style="position: absolute;left:50px" ></i>
			      
		          <span>归档</span>
                  </a>
                </li>
              
                <li>

                  <a href="/blog/tags " style="margin-left:75px">
				  
				   <i class="fa fas fa-tags" style="position: absolute;left:50px" ></i>
			      
		          <span>标签</span>
                  </a>
                </li>
              
                <li>

                  <a href="/blog/categories " style="margin-left:75px">
				  
				   <i class="fa fas fa-bookmark" style="position: absolute;left:50px" ></i>
			      
		          <span>分类</span>
                  </a>
                </li>
              
            </ul>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/blog/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友链
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="javascript:;">
			
				<i class="fa-fw fas fa-user-circle"></i>
			
			关于
			<span class="m-icon"><i class="fas fa-chevron-right"></i></span>
		</a>
            <ul  >
              
                <li>

                  <a href="/blog/about " style="margin-left:75px">
				  
				   <i class="fa fas fa-star-of-david" style="position: absolute;left:50px" ></i>
			      
		          <span>主页</span>
                  </a>
                </li>
              
                <li>

                  <a href="/blog/gallery " style="margin-left:75px">
				  
				   <i class="fa fas fa-images" style="position: absolute;left:50px" ></i>
			      
		          <span>相册</span>
                  </a>
                </li>
              
            </ul>
          
        </li>
        
        
    </ul>
</div>


        </div>

        
    </nav>

</header>

    
<script src="/blog/libs/cryptojs/crypto-js.min.js"></script>
<script>
    (function() {
        let pwd = '';
        if (pwd && pwd.length > 0) {
            if (pwd !== CryptoJS.SHA256(prompt('请输入访问本文章的密码')).toString(CryptoJS.enc.Hex)) {
                alert('密码错误，将返回主页！');
                location.href = '/blog/';
            }
        }
    })();
</script>




<div class="bg-cover pd-header post-cover" style="background-image: url('/blog/medias/featureimages/4.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 id="post-title" class="description center-align post-title"></h1>

                    
                        <!-- <script src="https://cdn.jsdelivr.net/npm/typed.js@2.0.11"></script> -->
                        <script>
                            var typedObj = new Typed("#post-title", {
                                strings: [ '阿里巴巴Java开发手册(泰山版)' ],
                                startDelay: 300,
                                typeSpeed: 70,
                                loop: false,
                                backSpeed: 50,
                                showCursor: true
                            });
                        </script>
                    
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/blog/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
        background-color: rgb(255, 255, 255,0.7);
        border-radius: 10px;
        box-shadow: 0 10px 35px 2px rgba(0, 0, 0, .15), 0 5px 15px rgba(0, 0, 0, .07), 0 2px 5px -5px rgba(0, 0, 0, .1) !important;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
        max-height: 480px;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                          <div class="article-tag">
                            <span class="chip bg-color">无标签</span>
                          </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/blog/categories/Java/" class="post-category">
                                Java
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                

                

                

                

                
            </div>
        </div>
        <hr class="clearfix">

        
        <!-- 是否加载使用自带的 prismjs. -->
        <link rel="stylesheet" href="/blog/libs/prism/prism.css">
        

        
        <!-- 代码块折行 -->
        <style type="text/css">
            code[class*="language-"], pre[class*="language-"] { white-space: pre-wrap !important; }
        </style>
        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h2 id="一、编程规约"><a href="#一、编程规约" class="headerlink" title="一、编程规约"></a>一、编程规约</h2><h3 id="一-命名风格"><a href="#一-命名风格" class="headerlink" title="(一) 命名风格"></a>(一) 命名风格</h3><ol>
<li><p><strong>【强制】</strong> 代码中的命名均不能以<strong>下划线或美元符号</strong>开始，也不能以<strong>下划线或美元符号</strong>结束。<br>反例：_name&#x2F;__name&#x2F;$name&#x2F;name_&#x2F;name$&#x2F;name__</p>
</li>
<li><p><strong>【强制】</strong> 所有编程相关的命名严禁使用拼音与英文混合的方式，更不允许直接使用中文的方式。<br>说明：正确的英文拼写和语法可以让阅读者易于理解，避免歧义。注意，纯拼音命名方式更要避免采用。<br>正例:ali&#x2F;alibaba&#x2F;taobao&#x2F;cainiao&#x2F;aliyun&#x2F;youku&#x2F;hangzhou等国际通用的名称，可视同英文。<br>反例：DaZhePromotion[打折]&#x2F; getPingfenByName()[评分]&#x2F; int 某变量&#x3D;3</p>
</li>
<li><p><strong>【强制】</strong> 类名使用UpperCamelCase风格，但以下情形例外：DO&#x2F;BO&#x2F;DTO&#x2F;VO&#x2F;AO&#x2F;PO&#x2F;UID等。<br>正例：ForceCode&#x2F;UserDO&#x2F;HtmlDTO&#x2F;XmlService&#x2F;TcpUdpDeal&#x2F;TaPromotion<br>反例：forcecode&#x2F;UserDo&#x2F;HTMLDto&#x2F;XMLService&#x2F;TCPUDPDeal&#x2F;TAPromotion</p>
</li>
<li><p><strong>【强制】</strong> 方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格。<br>正例：localValue&#x2F;getHttpMessage()&#x2F;inputUserId</p>
</li>
<li><p><strong>【强制】</strong> 常量命名全部大写，单词间用下划线隔开，力求语义表达完整清楚，不要嫌名字长。<br>正例：MAX_STOCK_COUNT&#x2F;CACHE_EXPIRED_TIME<br>反例：MAX_COUNT&#x2F;EXPIRED_TIME</p>
</li>
<li><p><strong>【强制】</strong> 抽象类命名使用Abstract或Base开头；异常类命名使用Exception结尾；测试类命名以它要测试的类的名称开始，以Test结尾。</p>
</li>
<li><p><strong>【强制】</strong> 类型与中括号紧挨相连来表示数组。<br>正例：定义整形数组int[] arrayDemo;<br>反例：在main参数中，使用String args[]来定义。</p>
</li>
<li><p><strong>【强制】</strong> POJO类中的任何布尔类型的变量，都不要加is前缀，否则部分框架解析会引起序列化错误。<br>说明：在本文MySQL规约中的建表约定第一条，表达是与否的值采用is_xxx的命名方式，所以，需要在&lt;resultMap&gt;设置从is_xxx到xxx的映射关系。<br>反例：定义为基本数据类型Boolean isDeleted的属性，它的方法也是isDeleted。框架在反向解析的时候，”误以为”对应的属性名称是deleted，导致属性获取不到，进而抛出异常。</p>
</li>
<li><p><strong>【强制】</strong> 包名统一使用小写，点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用<strong>单数</strong>形式，但是类名如果有复数含义，类名可以使用复数形式。<br>正例：应用工具类包名为com.alibaba.ei.kunlun.aap.util、类名为MessageUtils(此规则参考spring的框架结构)</p>
</li>
<li><p><strong>【强制】</strong> 避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名，使可读性降低。<br>说明：子类、父类成员变量名相同，即使是public类型的变量也是能够通过编译，而局部变量在同一方法内的不同代码块中同名也是合法的，但是要避免使用。对于非setter&#x2F;getter的参数名称也要避免与成员变量名称相同。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConfusingName</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">public</span> <span class="token keyword">int</span> stock<span class="token punctuation">;</span>
	<span class="token comment">// 非 setter/getter的参数名称，不允许与本类成员变量同名</span>
	<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token class-name">String</span> alibaba<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>condition<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
			<span class="token keyword">final</span> <span class="token keyword">int</span> money <span class="token operator">=</span> <span class="token number">666</span><span class="token punctuation">;</span>
			<span class="token comment">// ...</span>
		<span class="token punctuation">&#125;</span>
		<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token comment">//在同一方法体中，不允许与其它代码块中的money命名相同</span>
            <span class="token keyword">final</span> <span class="token keyword">int</span> money <span class="token operator">=</span> <span class="token number">15978</span><span class="token punctuation">;</span>
            <span class="token comment">// ...</span>
		<span class="token punctuation">&#125;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">class</span> <span class="token class-name">Son</span> <span class="token keyword">extends</span> <span class="token class-name">ConfusingName</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">//不允许与父类的成员变量名称相同</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> stock<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 杜绝完全不规范的缩写，避免望文不知义。<br>反例：AbstractClass”缩写”命名成AbsClass;condition”缩写”命名成condi，此类随意缩写严重降低了代码的可阅读性。</p>
</li>
<li><p>【推荐】为了达到代码自解释的目标，任何自定义编程元素在命名时，使用尽量完整的单词组合来表达。<br>正例：在JDK中对某个对象引用的volatile字段进行原子更新的类名为AtomicReferenceFieldUpdater。<br>反例：常见的方法内变量为<code>int a;</code>的定义方式。</p>
</li>
<li><p>【推荐】在常量与变量的命名时，表示类型的名词放在词尾，以提升辨识度。<br>正例：startTime&#x2F;workQueue&#x2F;nameList&#x2F;TERMINATED_THREAD_COUNT<br>反例：startedAt&#x2F;QueueOfWork&#x2F;listName&#x2F;COUNT_TERMINATED_THREAD</p>
</li>
<li><p>【推荐】如果模块、接口、类、方法使用了设计模式，在命名时需体现出具体模式。<br>说明：将设计模式体现在名字中，有利于阅读者快速理解架构设计理念。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderFactory</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginProxy</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ResourceObserver</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】接口类中的方法和属性不要加任何修饰符号（public也不要加），保持代码的简洁性，并加上有效的Javadoc注释。尽量不要在接口里定义变量，如果一定要定义变量，确定与接口方法相关，并且是整个应用的基础常量。<br>正例：接口方法签名<code>void commit();</code><br>接口基础常量<code>String COMPANY = &quot;alibaba&quot;;</code><br>反例：接口方法定义<code>public abstract void f();</code><br>说明：JDK8中接口允许有默认实现，那么这个default方法，是对所有实现类都有价值的默认实现。</p>
</li>
<li><p>接口和实现类的命名有两套规则：<br>1)<strong>【强制】</strong> 对于Service和DAO类，基于SOA的理念，暴露出来的服务一定是接口，内部的实现类用Impl的后缀与接口区别。<br>正例：CacheServiceImpl实现CacheService接口。<br>2)【推荐】如果是形容能力的接口名称，取对应的形容词为接口名（通常是-able的形容词）。正例：AbstractTranslator实现Translatable接口。</p>
</li>
<li><p>【参考】枚举类名带上Enum后缀，枚举成员名称需要全大写，单词间用下划线隔开。<br>说明：枚举其实就是特殊的常量类，且构造方法被默认强制是私有。<br>正例：枚举名字为ProcessStatusEnum的成员名称：SUCCESS&#x2F;UNKNOWN_REASON。</p>
</li>
<li><p>【参考】各层命名规约：<br>A)Service&#x2F;DAO层方法命名规约<br>1)获取单个对象的方法用get做前缀。<br>2)获取多个对象的方法用list做前缀，复数结尾，如：listObjects。<br>3)获取统计值的方法用count做前缀。<br>4)插入的方法用save&#x2F;insert做前缀。<br>5)删除的方法用remove&#x2F;delete做前缀。<br>6)修改的方法用update做前缀。<br>B)领域模型命名规约<br>1)数据对象：xxxDO，xxx即为数据表名。<br>2)数据传输对象：xxxDTO，xxx为业务领域相关的名称。<br>3)展示对象：xxxVO,xxx一般为网页名称。<br>4)POJO是DO&#x2F;DTO&#x2F;BO&#x2F;VO的统称，禁止命名成xxxPOJO。</p>
</li>
</ol>
<h3 id="二-常量定义"><a href="#二-常量定义" class="headerlink" title="(二) 常量定义"></a>(二) 常量定义</h3><ol>
<li><p><strong>【强制】</strong> 不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//本例中同学A定义了缓存的key，然后缓存提取的同学B使用了Id#taobao来提取，少了下划线，导致故障。</span>
<span class="token class-name">String</span> key <span class="token operator">=</span> <span class="token string">"Id#taobao_"</span> <span class="token operator">+</span> tradeId
cache<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在long或者Long赋值时，数值后使用大写的L，不能是小写的l,小写容易跟数字1混淆，造成误解。<br>说明：Long a &#x3D; 2l;写的是数字的21，还是Long型的2。</p>
</li>
<li><p>【推荐】不要使用一个常量类维护所有常量，要按常量功能进行归类，分开维护。<br>说明：大而全的常量类，杂乱无章，使用查找功能才能定位到修改的常量，不利于理解，也不利于维护。<br>正例：缓存相关常量放在类CacheConsts下；系统配置相关常量放在类ConfigConsts下。</p>
</li>
<li><p>【推荐】常量的复用层次有五层：跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。<br>1)跨应用共享常量：放置在二方库中，通常是client.jar中的constant目录下。<br>2)应用内共享常量：放置在一方库中，通常是子模块中的constant目录下。<br>反例：易懂变量也要统一定义成应用内共享常量，两位工程师在两个类中分别定义了”YES”的变量：<br>类A中：public static final String YES &#x3D; “yes”;<br>类B中：public static final String YES &#x3D; “y”;<br>A.YES.equals(B.YES)，预期是true，但实际返回为false，导致线上问题。<br>3)子工程内部共享常量：即在当前子工程的constant目录下。<br>4)包内共享常量：即在当前包下单独的constant目录下。<br>5)类内共享常量：直接在类内部private static final定义。</p>
</li>
<li><p>【推荐】如果变量值仅在一个固定范围内变化用enum类型来定义。<br>说明：如果存在名称之外的延伸属性应使用enum类型，下面正例中的数字就是延伸信息，表示一年中的第几个季节。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">SeasonEnum</span> <span class="token punctuation">&#123;</span>
	<span class="token function">SPRING</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">SUMMER</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">AUTUMN</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token constant">WINTER</span> ⑷；
	<span class="token keyword">private</span> <span class="token keyword">int</span> seq<span class="token punctuation">;</span>
	<span class="token class-name">SeasonEnum</span><span class="token punctuation">(</span><span class="token keyword">int</span> seq<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		<span class="token keyword">this</span><span class="token punctuation">.</span>seq <span class="token operator">=</span> seq<span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
	<span class="token keyword">public</span> <span class="token keyword">int</span> getSeqO <span class="token punctuation">&#123;</span>
		<span class="token keyword">return</span> seq
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li>
</ol>
<h3 id="三-代码格式"><a href="#三-代码格式" class="headerlink" title="(三) 代码格式"></a>(三) 代码格式</h3><ol>
<li><p><strong>【强制】</strong> 如果是大括号内为空，则简洁地写成{}即可，大括号中间无需换行和空格；如果是非空代码块则：<br>1)左大括号前不换行。<br>2)左大括号后换行。<br>3)右大括号前换行。<br>4)右大括号后还有else等代码则不换行；表示终止的右大括号后必须换行。</p>
</li>
<li><p><strong>【强制】</strong> 左小括号和右边相邻字符之间不出现空格；右小括号和左边相邻字符之间也不出现空格；而左大括号前需要加空格。详见第5条下方正例提示。<br>反例：if (空格a &#x3D;&#x3D; b空格)</p>
</li>
<li><p><strong>【强制】</strong> if&#x2F;for&#x2F;while&#x2F;switch&#x2F;do等保留字与括号之间都必须加空格。</p>
</li>
<li><p><strong>【强制】</strong> 任何二目、三目运算符的左右两边都需要加一个空格。<br>说明：包括赋值运算符&#x3D;、逻辑运算符&amp;&amp;、加减乘除符号等。</p>
</li>
<li><p><strong>【强制】</strong> 采用4个空格缩进，禁止使用tab字符。<br>说明：如果使用tab缩进，必须设置1个tab为4个空格。IDEA设置tab为4个空格时，请勿勾选Use tab character;而在eclipse中，必须勾选insert spaces for tabs<br>正例：(涉及1-5点)</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">//缩进4个空格</span>
    <span class="token class-name">String</span> say <span class="token operator">=</span> <span class="token string">"hello"</span><span class="token punctuation">;</span>
    <span class="token comment">//运算符的左右必须有一个空格</span>
    <span class="token keyword">int</span> flag <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token comment">//关键词if与括号之间必须有一个空格，括号内的f与左括号，0与右括号不需要空格</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>flag <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    	<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span> <span class="token function">println</span><span class="token punctuation">(</span>say<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    <span class="token comment">//左大括号前加空格且不换行；左大括号后换行</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>flag <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    	<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span> <span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//右大括号前换行，右大括号后有else，不用换行</span>
    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
    	<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"ok"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//在右大括号后直接结束，则必须换行</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 注释的双斜线与注释内容之间有且仅有一个空格。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// 这是示例注释，请注意在双斜线之后有一个空格</span>
<span class="token class-name">String</span> commentstring <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringO</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在进行类型强制转换时，右括号与强制转换值之间不需要任何空格隔开。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">long</span> first <span class="token operator">=</span> <span class="token number">1000000000000L</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> second <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>first <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 单行字符数限制不超过120个，超出需要换行，换行时遵循如下原则：<br>1)第二行相对第一行缩进4个空格，从第三行开始，不再继续缩进，参考示例。<br>2)运算符与下文一起换行。<br>3)方法调用的点符号与下文一起换行。<br>4)方法调用中的多个参数需要换行时，在逗号后进行。<br>5)在括号前不要换行，见反例<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">StringBuilder</span> sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">;</span>
<span class="token comment">//超过120个字符的情况下，换行缩进4个空格，并且方法前的点号一起换行sb.append("zi").append("xin")...</span>
<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"huang"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"huang"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"huang"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">StringBuilder</span> sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">;</span>
<span class="token comment">//超过120个字符的情况下，不要在括号前换行</span>
sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"you"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"are"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>append
<span class="token punctuation">(</span><span class="token string">"lucky"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//参数很多的方法调用可能超过120个字符，逗号后才是换行处</span>
<span class="token function">method</span><span class="token punctuation">(</span>args1<span class="token punctuation">,</span> args2<span class="token punctuation">,</span> args3<span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">,</span>argsX<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 方法参数在定义和传入时，多个参数逗号后边必须加空格。<br>正例：下例中实参的<strong>args1</strong>，后边必须要有一个空格。</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token function">method</span><span class="token punctuation">(</span>args1<span class="token punctuation">,</span> args2<span class="token punctuation">,</span> args3<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> IDE的text file encoding设置为UTF-8;IDE中文件的换行符使用Unix格式，不要使用Windows格式。</p>
</li>
<li><p>【推荐】单个方法的总行数不超过80行。<br>说明：除注释之外的方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行。<br>正例：代码逻辑分清红花和绿叶，个性和共性，绿叶逻辑单独出来成为额外方法，使主干代码更加清晰；共性逻辑抽取成为共性方法，便于复用和维护。</p>
</li>
<li><p>【推荐】没有必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> one <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token keyword">long</span> two <span class="token operator">=</span> <span class="token number">2L</span><span class="token punctuation">;</span>
<span class="token keyword">float</span> three <span class="token operator">=</span> <span class="token number">3F</span><span class="token punctuation">;</span>
<span class="token class-name">StringBuilder</span> sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>说明：增加sb这个变量，如果需要对齐，则给one、two、three都要增加几个空格，在变量比较多的情况下，是非常累赘的事情。</p>
</li>
<li><p>【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。<br>说明：任何情形，没有必要插入<strong>多个空行</strong>进行隔开。</p>
</li>
</ol>
<h3 id="四-OOP规约"><a href="#四-OOP规约" class="headerlink" title="(四) OOP规约"></a>(四) OOP规约</h3><ol>
<li><p><strong>【强制】</strong> 避免通过一个类的对象引用访问此类的静态变量或静态方法，无谓增加编译器解析成本，直接用<strong>类名</strong>来访问即可。</p>
</li>
<li><p><strong>【强制】</strong> 所有的覆写方法，必须加@Override注解。<br>说明：getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外，如果在抽象类中对方法签名进行修改，其实现类会马上编译报错。</p>
</li>
<li><p><strong>【强制】</strong> 相同参数类型，相同业务含义，才可以使用Java的可变参数，避免使用Object。<br>说明：可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">User</span><span class="token punctuation">></span></span> <span class="token function">listUsers</span><span class="token punctuation">(</span><span class="token class-name">String</span> type<span class="token punctuation">,</span> <span class="token class-name">Long</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> ids<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 外部正在调用或者二方库依赖的接口，不允许修改方法签名，避免对接口调用方产生影响。接口过时必须加@Deprecated注解，并清晰地说明采用的新接口或者新服务是什么。</p>
</li>
<li><p><strong>【强制】</strong> 不能使用过时的类或方法。<br>说明：java.net.URLDecoder中的方法decode(String encodeStr)这个方法已经过时，应该使用双参数decode(String source, String encode)。接口提供方既然明确是过时接口，那么有义务同时提供新的接口；作为调用方来说，有义务去考证过时方法的新实现是什么。</p>
</li>
<li><p><strong>【强制】</strong> Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。<br>正例:”test”.equals(object);<br>反例:object.equals(“test”);<br>说明：推荐使用java.util.Objects#equals(JDK7引入的工具类)。</p>
</li>
<li><p><strong>【强制】</strong> 所有整型包装类对象之间<strong>值的比较</strong>，全部使用equals方法比较。<br>说明：对于Integer var &#x3D; ?在 <strong>-128至127</strong> 之间的赋值，Integer对象是在IntegerCache.cache产生，会复用已有对象，这个区间内的Integer值可以直接使用&#x3D;&#x3D;进行判断，但是这个区间之外的所有数据，都会在堆上产生，并不会复用已有对象，这是一个大坑，推荐使用equals方法进行判断。</p>
</li>
<li><p><strong>【强制】</strong> 任何货币金额，均以最小货币单位且整型类型来进行存储。</p>
</li>
<li><p><strong>【强制】</strong> 浮点数之间的等值判断，基本数据类型不能用&#x3D;&#x3D;来比较，包装数据类型不能用equals来判断。<br>说明：浮点数采用”尾数+阶码”的编码方式，类似于科学计数法的”有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数，具体原理参考《码出高效》<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">float</span> a <span class="token operator">=</span> <span class="token number">1.0f</span> <span class="token operator">-</span> <span class="token number">0.9f</span><span class="token punctuation">;</span>
<span class="token keyword">float</span> b <span class="token operator">=</span> <span class="token number">0.9f</span> <span class="token operator">-</span> <span class="token number">0.8f</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>a <span class="token operator">==</span> b<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">//预期进入此代码快，执行其它业务逻辑</span>
	<span class="token comment">//但事实上a==b的结果为false</span>
<span class="token punctuation">&#125;</span>
<span class="token class-name">Float</span> x <span class="token operator">=</span> <span class="token class-name">Float</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Float</span> y <span class="token operator">=</span> <span class="token class-name">Float</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>x<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">//预期进入此代码快，执行其它业务逻辑</span>
	<span class="token comment">//但事实上equals的结果为false</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>正例：<br>(1)指定一个误差范围，两个浮点数的差值在此范围之内，则认为是相等的。</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">float</span> a <span class="token operator">=</span> <span class="token number">1.0f</span> <span class="token operator">-</span> <span class="token number">0.9f</span><span class="token punctuation">;</span>
<span class="token keyword">float</span> b <span class="token operator">=</span> <span class="token number">0.9f</span> <span class="token operator">-</span> <span class="token number">0.8f</span><span class="token punctuation">;</span>
<span class="token keyword">float</span> diff <span class="token operator">=</span> <span class="token number">1e-6f</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span>a <span class="token operator">-</span> b<span class="token punctuation">)</span> <span class="token operator">&lt;</span> diff<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"true"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>(2)使用BigDecimal来定义值，再进行浮点数的运算操作。</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">BigDecimal</span> a <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"1.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">BigDecimal</span> b <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"0.9"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">BigDecimal</span> c <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"0.8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">BigDecimal</span> x <span class="token operator">=</span> a<span class="token punctuation">.</span><span class="token function">subtract</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">BigDecimal</span> y <span class="token operator">=</span> b<span class="token punctuation">.</span><span class="token function">subtract</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>x<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"true"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 定义数据对象DO类时，属性类型要与数据库字段类型相匹配。<br>正例：数据库字段的bigint必须与类属性的Long类型相对应。<br>反例：某个案例的数据库表id字段定义类型bigint unsigned，实际类对象属性为Integer，随着id越来越大，超过Integer的表示范围而溢出成为负数。</p>
</li>
<li><p><strong>【强制】</strong> 禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象<br>说明：BigDecimal(double)存在精度损失风险，在精确计算或值比较的场景中可能会导致业务逻辑异常。如：BigDecimal g &#x3D; new BigDecimal(0.1f);实际的存储值为:0.10000000149<br>正例：优先推荐入参为String的构造方法，或使用BigDecimal的valueOf方法，此方法内部其实执行了Double的toString，而Double的toString按double的实际能表达的精度对尾数进行了截断。</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">BigDecimal</span> recommend1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"0.1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">BigDecimal</span> recommend2 <span class="token operator">=</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span><span class="token number">0.1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
</li>
<li><p>关于基本数据类型与包装数据类型的使用标准如下：<br>1)<strong>【强制】</strong> 所有的POJO类属性必须使用包装数据类型。<br>2)<strong>【强制】</strong> RPC方法的返回值和参数必须使用包装数据类型。<br>3)<strong>【推荐】</strong> 所有的局部变量使用基本数据类型。<br>说明：POJO类属性没有初值是提醒使用者在需要使用时，必须自己显式地进行赋值，任何NPE问题，或者入库检查，都由使用者来保证。<br>正例：数据库的查询结果可能是null，因为自动拆箱，用基本数据类型接收有NPE风险。<br>反例：某业务的交易报表上显示成交总额涨跌情况，即正负x%，x为基本数据类型，调用的RPC服务，调用不成功时，返回的是默认值，页面显示为0%，这是不合理的，应该显示成中划线-。所以包装数据类型的null值，能够表示额外的信息，如：远程调用失败，异常退出。</p>
</li>
<li><p><strong>【强制】</strong> 定义DO&#x2F;DTO&#x2F;VO等POJO类时，不要设定任何属性<strong>默认值</strong><br>反例：POJO类的createTime默认值为new Date()，但是这个属性在数据提取时并没有置入具体值，在更新其它字段时又附带更新了此字段，导致创建时间被修改成当前时间。</p>
</li>
<li><p><strong>【强制】</strong> 序列化类新增属性时，请不要修改serialVersionUID字段，避免反序列失败；如果完全不兼容升级，避免反序列化混乱，那么请修改serialVersionUID值。<br>说明：注意serialVersionUID不一致会抛出序列化运行时异常。</p>
</li>
<li><p><strong>【强制】</strong> 构造方法里面禁止加入任何业务逻辑，如果有初始化逻辑，请放在init方法中。</p>
</li>
<li><p><strong>【强制】</strong> POJO类必须写toString方法。使用IDE中的工具：source&gt;generate toString时，如果继承了另一个POJO类，注意在前面加一下super.toString。<br>说明：在方法执行抛出异常时，可以直接调用POJO的toString()方法打印其属性值，便于排查问题。</p>
</li>
<li><p><strong>【强制】</strong> 禁止在POJO类中，同时存在对应属性xxx的isXxx()和getXxx()方法。<br>说明：框架在调用属性xxx的提取方法时，并不能确定哪个方法一定是被优先调用到，神坑之一。</p>
</li>
<li><p>【推荐】使用索引访问用String的split方法得到的数组时，需做最后一个分隔符后有无内容的检查，否则会有抛IndexOutOfBoundsException的风险。<br>说明：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">String</span> str <span class="token operator">=</span> <span class="token string">"a,b,c„"</span><span class="token punctuation">;</span>
<span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> ary <span class="token operator">=</span> str<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//预期大于3,结果是3</span>
<span class="token class-name">System</span><span class="token punctuation">.</span> out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>ary<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li>
<li><p>【推荐】当一个类有多个构造方法，或者多个同名方法，这些方法应该按顺序放置在一起，便于阅读，此条规则优先于下一条。</p>
</li>
<li><p>【推荐】类内方法定义的顺序依次是：公有方法或保护方法&gt;私有方法&gt;getter&#x2F;setter方法。<br>说明：公有方法是类的调用者和维护者最关心的方法，首屏展示最好；保护方法虽然只是子类关心，也可能是”模板设计模式”下的核心方法；而私有方法外部一般不需要特别关心，是一个黑盒实现；因为承载的信息价值较低，所有Service和DAO的getter&#x2F;setter方法放在类体最后。</p>
</li>
<li><p>【推荐】setter方法中，参数名称与类成员变量名称一致，this.成员名&#x3D;参数名。在getter&#x2F;setter方法中，不要增加业务逻辑，增加排查问题的难度。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token class-name">Integer</span> getData <span class="token number">0</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span>condition<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		<span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>data <span class="token operator">+</span> <span class="token number">100</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
		<span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>data <span class="token operator">-</span> <span class="token number">100</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】循环体内，字符串的连接方式，使用StringBuilder的append方法进行扩展。<br>说明：下例中反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象然后进行append操作，最后通过toString方法返回String对象，造成内存资源浪费。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">String</span> str <span class="token operator">=</span> <span class="token string">"start"</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	str <span class="token operator">=</span> str <span class="token operator">+</span> <span class="token string">"hello"</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】final可以声明类、成员变量、方法、以及本地变量，下列情况使用final关键字：<br>1)不允许被继承的类，如：String类。<br>2)不允许修改引用的域对象，如：POJO类的域变量。<br>3)不允许被覆写的方法，如：POJO类的setter方法。<br>4)不允许运行过程中重新赋值的局部变量。<br>5)避免上下文重复使用一个变量，使用final可以强制重新定义一个变量，方便更好地进行重构。</p>
</li>
<li><p>【推荐】慎用Object的clone方法来拷贝对象。<br>说明：对象clone方法默认是浅拷贝，若想实现深拷贝需覆写clone方法实现域对象的深度遍历式拷贝。</p>
</li>
<li><p>【推荐】类成员与方法访问控制从严：<br>1)如果不允许外部直接通过new来创建对象，那么构造方法必须是private。<br>2)工具类不允许有public或default构造方法。<br>3)类非static成员变量并且与子类共享，必须是protectedo<br>4)类非static成员变量并且仅在本类使用，必须是private。<br>5)类static成员变量如果仅在本类使用，必须是private。<br>6)若是static成员变量，考虑是否为final。<br>7)类成员方法只供类内部调用，必须是private,<br>8)类成员方法只对继承类公开，那么限制为protected,<br>说明：任何类、方法、参数、变量，严控访问范围。过于宽泛的访问范围，不利于模块解耦。思考：如果是一个private的方法，想删除就删除，可是一个public的service成员方法或成员变量，删除一下，不得手心冒点汗吗？变量像自己的小孩，尽量在自己的视线内，变量作用域太大，无限制的到处跑，那么你会担心的。</p>
</li>
</ol>
<h3 id="五-日期时间"><a href="#五-日期时间" class="headerlink" title="(五) 日期时间"></a>(五) 日期时间</h3><ol>
<li><p><strong>【强制】</strong> 日期格式化时，传入pattern中表示年份统一使用小写的y。<br>说明：日期格式化时，yyyy表示当天所在的年，而大写的YYYY代表是week in which year(JDK7之后引入的概念)，意思是当天所在的周属于的年份，一周从周日开始，周六结束，只要本周跨年，返回的YYYY就是下一年。<br>正例：表示日期和时间的格式如下所示：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">new</span> <span class="token class-name">SimpleDateFormat</span><span class="token punctuation">(</span><span class="token string">"yyyy-MM-dd HH:mm:ss"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在日期格式中分清楚大写的M和小写的m,大写的H和小写的h分别指代的意义。<br>说明：日期格式中的这两对字母表意如下：<br>1)表示月份是大写的M；<br>2)表示分钟则是小写的m；<br>3)24小时制的是大写的H；<br>4)12小时制的则是小写的h。</p>
</li>
<li><p><strong>【强制】</strong> 获取当前毫秒数：System.currentTimeMillis();而不是new Date().getTime()。<br>说明：如果想获取更加精确的纳秒级时间值，使用System.nanoTime的方式。在JDK8中，针对统计时间等场景，推荐使用Instant类。</p>
</li>
<li><p><strong>【强制】</strong> 不允许在程序任何地方中使用：1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp。<br>说明：第1个不记录时间，getHours()抛出异常；第2个不记录日期，getYear()抛出异常；第3个在构造方法super((time/1000)*1000),fastTime和nanos分开存储秒和纳秒信息。<br>反例：java.util.Date.after(Date)进行时间比较时，当入参是java.sql.Timestamp时，会触发JDK BUG(JDK9已修复)，可能导致比较时的意外结果。</p>
</li>
<li><p><strong>【强制】</strong> 不要在程序中写死一年为365天，避免在公历闰年时出现日期转换错误或程序逻辑错误。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//获取今年的天数</span>
<span class="token keyword">int</span> daysOfThisYear <span class="token operator">=</span> <span class="token class-name">LocalDate</span><span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">lengthOfYear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//获取指定某年的天数</span>
<span class="token class-name">LocalDate</span><span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span><span class="token number">2011</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">lengthOfYear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//第一种情况：在闰年366天时，出现数组越界异常</span>
<span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dayArray <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token number">365</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token comment">//第二种情况：一年有效期的会员制，今年1月26日注册，硬编码365返回的却是1月25日</span>
<span class="token class-name">Calendar</span> calendar <span class="token operator">=</span> <span class="token class-name">Calendar</span><span class="token punctuation">.</span>getlnstance<span class="token punctuation">;</span>
calendar<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token number">2020</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">26</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
calendar<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token class-name">Calendar</span><span class="token punctuation">.</span><span class="token constant">DATE</span><span class="token punctuation">,</span> <span class="token number">365</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】避免公历闰年2月问题。闰年的2月份有29天，一年后的那一天不可能是2月29日。</p>
</li>
<li><p>【推荐】使用枚举值来指代月份。如果使用数字，注意Date，Calendar等日期相关类的月份month取值在0-11之间。<br>说明：参考JDK原生注释，Month value is 0-based. e.g., 0 for January.<br>正例：CalendarJANUARY，Calendar.FEBRUARY，Calendar.MARCH等来指代相应月份来进行传参或比较。</p>
</li>
</ol>
<h3 id="六-集合处理"><a href="#六-集合处理" class="headerlink" title="(六) 集合处理"></a>(六) 集合处理</h3><ol>
<li><p><strong>【强制】</strong> 关于hashCode和equals的处理，遵循如下规则：<br>1)只要重写equals，就必须重写hashCode。<br>2)因为Set存储的是不重复的对象，依据hashCode和equals进行判断，所以Set存储的对象必须重写这两个方法。<br>3)如果自定义对象作为Map的键，那么必须覆写hashCode和equals。<br>说明：String因为重写了hashCode和equals方法，所以我们可以愉快地使用String对象作为key来使用。</p>
</li>
<li><p><strong>【强制】</strong> 判断所有集合内部的元素是否为空，使用isEmpty()方法，而不是size()&#x3D;&#x3D;0的方式。<br>说明：前者的时间复杂度为O(1),而且可读性更好。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>map<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"no element in this map."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在使用java.util.stream.Collectors类的toMap()方法转为Map集合时，一定要使用含有参数类型为BinaryOperator,参数名为mergeFunction的方法，否则当出现相同key值时会抛出IllegalStateException异常。<br>说明：参数mergeFunction的作用是当出现key重复时，自定义对value的处理策略。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Pair</span><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Double</span><span class="token punctuation">></span><span class="token punctuation">></span></span> pairArrayList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayLists</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
pairArrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Pair</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token string">"version"</span><span class="token punctuation">,</span> <span class="token number">6.19</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
pairArrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Pair</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token string">"version"</span><span class="token punctuation">,</span> <span class="token number">10.24</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
pairArrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Pair</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token string">"version"</span><span class="token punctuation">,</span> <span class="token number">13.14</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Double</span><span class="token punctuation">></span></span> map <span class="token operator">=</span> pairArrayList<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>
<span class="token comment">//生成的map集合中只有一个键值对：&#123;version=13.14&#125;</span>
<span class="token class-name">Collectors</span><span class="token punctuation">.</span><span class="token function">toMap</span><span class="token punctuation">(</span><span class="token class-name">Pair</span><span class="token operator">::</span><span class="token function">getKey</span><span class="token punctuation">,</span> <span class="token class-name">Pair</span><span class="token operator">::</span><span class="token function">getValue</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>v1<span class="token punctuation">,</span> v2<span class="token punctuation">)</span> <span class="token operator">-></span> v2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> departments <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">&#123;</span><span class="token string">"iERP"</span><span class="token punctuation">,</span> <span class="token string">"iERP"</span><span class="token punctuation">,</span> <span class="token string">"EIBU"</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>
<span class="token comment">// 抛出IllegalStateException异常</span>
<span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">></span></span> map <span class="token operator">=</span> <span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span>departments<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span><span class="token class-name">Collectors</span><span class="token punctuation">.</span><span class="token function">toMap</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token operator">::</span><span class="token function">hashCode</span><span class="token punctuation">,</span> str <span class="token operator">-></span> str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在使用java.util.stream.Collectors类的toMap()方法转为Map集合时，一定要注意当value为null时会抛NPE异常。<br>说明：在java.util.HashMap的merge方法里会进行如下的判断：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> remappingFunction <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
	<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Pair</span><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Double</span><span class="token punctuation">></span><span class="token punctuation">></span></span> pairArrayList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
pairArrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Pair</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token string">"version1"</span><span class="token punctuation">,</span> <span class="token number">4.22</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
pairArrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Pair</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token string">"version2"</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Double</span><span class="token punctuation">></span></span> map <span class="token operator">=</span> pairArrayList<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>
<span class="token comment">// 抛出NullPointerException异常</span>
<span class="token class-name">Collectors</span><span class="token punctuation">.</span><span class="token function">toMap</span><span class="token punctuation">(</span><span class="token class-name">Pair</span><span class="token operator">::</span><span class="token function">getKey</span><span class="token punctuation">,</span> <span class="token class-name">Pair</span><span class="token operator">::</span><span class="token function">getValue</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>v1<span class="token punctuation">,</span> v2<span class="token punctuation">)</span> <span class="token operator">-></span> v2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> ArrayList的subList结果不可强转成ArrayList，否则会抛出ClassCastException异常：java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。<br>说明：subList返回的是ArrayList的内部类SubList，并不是ArrayList而是ArrayList的一个视图，对于SubList子列表的所有操作最终会反映到原列表上。</p>
</li>
<li><p><strong>【强制】</strong> 使用Map的方法keySet()&#x2F;values()&#x2F;entrySet()返回集合对象时，不可以对其进行添加元素操作，否则会抛出UnsupportedOperationException异常。</p>
</li>
<li><p><strong>【强制】</strong> Collections类返回的对象，如：emptyList()&#x2F;singietonList()等都是immutable list，不可对其进行添加或者删除元素的操作。<br>反例：如果查询无结果，返回Collections.emptyList()空集合对象，调用方一旦进行了添加元素的操作，就会触发UnsupportedOperationException异常。</p>
</li>
<li><p><strong>【强制】</strong> 在subList场景中，<strong>高度注意</strong>对父集合元素的增加或删除，均会导致子列表的遍历、增加、删除产生ConcurrentModificationException异常。</p>
</li>
<li><p><strong>【强制】</strong> 使用集合转数组的方法，必须使用集合的toArray(T[] array)，传入的是类型完全一致、长度为0的空数组。<br>反例：直接使用toArray无参方法存在问题，此方法返回值只能是Object[]类，若强转其它类型数组将出现ClassCastException错误。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"guan"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"bao"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> array <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>说明：使用toArray带参方法，数组空间大小的length，<br>1)<strong>等于0</strong>,动态创建与size相同的数组，性能最好。<br>2)<strong>大于0但小于size</strong>，重新创建大小等于size的数组，增加GC负担。<br>3)<strong>等于size</strong>，在高并发情况下，数组创建完成之后，size正在变大的情况下，负面影响与2相同。<br>4)<strong>大于size</strong>，空间浪费，且在size处插入null值，存在NPE隐患。</p>
</li>
<li><p><strong>【强制】</strong> 在使用Collection接口任何实现类的addAll()方法时，都要对输入的集合参数进行NPE判断。<br>说明：在ArrayList#addAII方法的第一行代码即Object[] a &#x3D; c.toArray();其中c为输入集合参数，如果为null，则直接抛出异常。</p>
</li>
<li><p><strong>【强制】</strong> 使用工具类Arrays.asList()把数组转换成集合时，不能使用其修改集合相关的方法，它的add&#x2F;remove&#x2F;clear方法会抛出UnsupportedOperationException异常。<br>说明：asList的返回对象是一个Arrays内部类，并没有实现集合的修改方法。Arrays.asList体现的是适配器模式，只是转换接口，后台的数据仍是数组。</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> str <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">&#123;</span> <span class="token string">"yang"</span><span class="token punctuation">,</span> <span class="token string">"hao"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>
<span class="token class-name">List</span> list <span class="token operator">=</span> <span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p>第一种情况：list.add(“yangguanba”)；运行时异常。<br>第二种情况：str[0] &#x3D; “changed”;也会随之修改，反之亦然。</p>
</li>
<li><p><strong>【强制】</strong> 泛型通配符&lt; ? extends T &gt;来接收返回的数据，此写法的泛型集合不能使用add方法，而&lt; ? super T &gt;不能使用get方法，两者在接口调用赋值的场景中容易出错。<br>说明：扩展说一下PECS(Producer Extends Consumer Super)原则：第一、频繁往外读取内容的，适合用&lt; ? extends T &gt;。第二、经常往里插入的，适合用&lt; ? super T &gt;</p>
</li>
<li><p><strong>【强制】</strong> 在无泛型限制定义的集合赋值给泛型限制的集合时，在使用集合元素时，需要进行instanceof判断，避免抛出ClassCastException异常。<br>说明：毕竟泛型是在JDK5后才出现，考虑到向前兼容，编译器是允许非泛型集合与泛型集合互相赋值。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> generics <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token class-name">List</span> notGenerics <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
notGenerics<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
notGenerics<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
generics <span class="token operator">=</span> notGenerics<span class="token punctuation">;</span>
<span class="token comment">// 此处抛出ClassCastException异常</span>
<span class="token class-name">String</span> string <span class="token operator">=</span> generics<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 不要在foreach循环里进行元素的remove&#x2F;add操作。remove元素请使用Iterator方式，如果并发操作，需要对Iterator对象加锁。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> iterator <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
	<span class="token class-name">String</span> item <span class="token operator">=</span> iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span>删除元素的条件<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
		iterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">String</span> item <span class="token operator">:</span> list<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        list<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>说明：以上代码的执行结果肯定会出乎大家的意料，那么试一下把”1”换成”2”，会是同样的结果吗？</p>
</li>
<li><p><strong>【强制】</strong> 在JDK7版本及以上，Comparator实现类要满足如下三个条件，不然Arrays.sort，Collections.sort会抛IllegalArgumentException异常。<br>说明：三个条件如下<br>1)x，y的比较结果和y，x的比较结果相反。<br>2)x&gt;y,y&gt;z,则x&gt;z。<br>3)x&#x3D;y，则x，z比较结果和y，z比较结果相同。<br>反例：下例中没有处理相等的情况，交换两个对象判断结果并不互反，不符合第一个条件，在实际使用中可能会出现异常。</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">new</span> <span class="token class-name">Comparator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Student</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">compare</span><span class="token punctuation">(</span><span class="token class-name">Student</span> o1<span class="token punctuation">,</span> <span class="token class-name">Student</span> o2<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    	<span class="token keyword">return</span> o1<span class="token punctuation">.</span><span class="token function">getld</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">></span> o2<span class="token punctuation">.</span><span class="token function">getld</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】集合泛型定义时，在JDK7及以上，使用diamond语法或全省略。<br>说明：菱形泛型，即diamond，直接使用&lt;&gt;来指代前边已经指定的类型。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// diamond方式,即&lt;></span>
<span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">></span></span> userCache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//全省略方式</span>
<span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">User</span><span class="token punctuation">></span></span> users <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】集合初始化时，指定集合初始值大小。<br>说明：HashMap使用HashMap(int initialcapacity)初始化，如果暂时无法确定集合大小，那么指定默认值(16)即可。<br>正例：initialcapacity&#x3D;(需要存储的元素个数&#x2F;负载因子)+1。注意负载因子(即loader factor)默认为0.75，如果暂时无法确定初始值大小，请设置为16(即默认值)。<br>反例：HashMap需要放置1024个元素，由于没有设置容量初始大小，随着元素不断增加，容量7次被迫扩大，resize需要重建hash表。当放置的集合元素个数达千万级别时，不断扩容会严重影响性能。</p>
</li>
<li><p>【推荐】使用entrySet遍历Map类集合KV，而不是keySet方式进行遍历。<br>说明：keySet其实是遍历了2次，一次是转为Iterator对象，另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中，效率更高。如果是JDK8，使用Map.forEach方法。<br>正例：values()返回的是V值集合，是一个list集合对象；keySet()返回的是K值集合，是一个Set集合对象；entrySet()返回的是K-V值组合集合。</p>
</li>
<li><p>【推荐】高度注意Map类集合K&#x2F;V能不能存储null值的情况，如下表格：</p>
</li>
</ol>
<table>
<thead>
<tr>
<th>集合类</th>
<th>Key</th>
<th>Value</th>
<th>Super</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>Hashtable</td>
<td>不允许为null</td>
<td>不允许为null</td>
<td>Dictionary</td>
<td>线呈安全</td>
</tr>
<tr>
<td>ConcurrentHashMap</td>
<td>不允许为null</td>
<td>不允许为null</td>
<td>AbstractMap</td>
<td>锁分段技术(JDK8:CAS )</td>
</tr>
<tr>
<td>TreeMap</td>
<td>不允许为null</td>
<td><strong>允许为null</strong></td>
<td>AbstractMap</td>
<td>线呈不安全</td>
</tr>
<tr>
<td>HashM ap</td>
<td><strong>允许为null</strong></td>
<td><strong>允许为null</strong></td>
<td>AbstractMap</td>
<td>线呈不安全</td>
</tr>
</tbody></table>
<p>反例：由于HashMap的干扰，很多人认为ConcurrentHashMap是可以置入null值，而事实上，存储null值时会抛出NPE异常。</p>
<ol start="20">
<li><p>【参考】合理利用好集合的有序性(sort)和稳定性(order)，避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。<br>说明：有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是一定的。如：ArrayList是order&#x2F;unsort;HashMap是unorder&#x2F;unsort;TreeSet是order&#x2F;sort。</p>
</li>
<li><p>【参考】利用Set元素唯一的特性，可以快速对一个集合进行去重操作，避免使用List的contains()进行遍历去重或者判断包含操作。</p>
</li>
</ol>
<h3 id="七-并发处理"><a href="#七-并发处理" class="headerlink" title="(七) 并发处理"></a>(七) 并发处理</h3><ol>
<li><p><strong>【强制】</strong> 获取单例对象需要保证线程安全，其中的方法也要保证线程安全。<br>说明：资源驱动类、工具类、单例工厂类都需要注意。</p>
</li>
<li><p><strong>【强制】</strong> 创建线程或线程池时请指定有意义的线程名称，方便出错时回溯。<br>正例：自定义线程工厂，并且根据外部特征进行分组，比如，来自同一机房的调用，把机房编号赋值给whatFeaturOfGroup</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserThreadFactory</span> <span class="token keyword">implements</span> <span class="token class-name">ThreadFactory</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">String</span> namePrefix<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">AtomicInteger</span> nextId <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Atomiclnteger</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//定义线程组名称，在jstack问题排查时，非常有帮助</span>
    <span class="token class-name">UserThreadFactory</span><span class="token punctuation">(</span><span class="token class-name">String</span> whatFeaturOfGroup<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        namePrefix <span class="token operator">=</span> <span class="token string">"From UserThreadFactory's "</span> <span class="token operator">+</span> whatFeaturOfGroup <span class="token operator">+</span> <span class="token string">"-Worker-"</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>

    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token class-name">Thread</span> <span class="token function">newThread</span><span class="token punctuation">(</span><span class="token class-name">Runnable</span> task<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token class-name">String</span> name <span class="token operator">=</span> namePrefix <span class="token operator">+</span> nextld<span class="token punctuation">.</span><span class="token function">getAndlncrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">Thread</span> thread <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> task<span class="token punctuation">,</span> name<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>thread<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> thread<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 线程资源必须通过线程池提供，不允许在应用中自行显式创建线程。<br>说明：线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销，解决资源不足的问题。如果不使用线程池，有可能造成系统创建大量同类线程而导致消耗完内存或者”过度切换”的问题。</p>
</li>
<li><p><strong>【强制】</strong> 线程池不允许使用Executors去创建，而是通过ThreadPoolExecutor的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险。<br>说明：Executors返回的线程池对象的弊端如下：<br>1)FixedThreadPool和SingleThreadPool:<br>允许的请求队列长度为lnteger.MAX_VALUE，可能会堆积大量的请求，从而导致OOM。<br>2)CachedThreadPool:<br>允许的创建线程数量为Integer.MAX_VALUE，可能会创建大量的线程，从而导致OOM。</p>
</li>
<li><p><strong>【强制】</strong> SimpleDateFormat是线程不安全的类，一般不要定义为static变量，如果定义为static，必须加锁，或者使用DateUtils工具类。<br>正例：注意线程安全，使用DateUtils。亦推荐如下处理：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">DateFormat</span><span class="token punctuation">></span></span> df <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation">&lt;</span> <span class="token class-name">DateFormat</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">protected</span> <span class="token class-name">DateFormat</span> <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">SimpleDateFormat</span><span class="token punctuation">(</span><span class="token string">"yyyy-MM-dd"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>说明：如果是JDK8的应用，可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat，官方给出的解释：simple beautiful strong immutable thread-safe。</p>
</li>
<li><p><strong>【强制】</strong> 必须回收自定义的ThreadLocal变量，尤其在线程池场景下，线程经常会被复用，如果不清理自定义的ThreadLocal变量，可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java">objectThreadLocal<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>userlnfo<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
<span class="token comment">// ...</span>
<span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>
	objectThreadLocal<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 高并发时，同步调用应该去考量锁的性能损耗。能用无锁数据结构，就不要用锁；能锁区块，就不要锁整个方法体；能用对象锁，就不要用类锁。<br>说明：尽可能使加锁的代码块工作量尽可能的小，避免在锁代码块中调用RPC方法。</p>
</li>
<li><p><strong>【强制】</strong> 对多个资源、数据库表、对象同时加锁时，需要保持一致的加锁顺序，否则可能会造成死锁。<br>说明：线程一需要对表A、B、C依次全部加锁后才可以进行更新操作，那么线程二的加锁顺序也必须是A、B、C，否则可能出现死锁。</p>
</li>
<li><p><strong>【强制】</strong> 在使用阻塞等待获取锁的方式中，必须在try代码块之外，并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用，避免加锁成功后，在finally中无法解锁。<br>说明一：如果在lock方法与try代码块之间的方法调用抛出异常，那么无法解锁，造成其它线程无法成功获取锁。<br>说明二：如果lock方法在try代码块之内，可能由于其它方法抛出异常，导致在finally代码块中，unlock对未加锁的对象解锁，它会调用AQS的tryRelease方法(取决于具体实现类)，抛出IllegalMonitorStateException异常。<br>说明三：在Lock对象的lock方法实现中可能抛出unchecked异常，产生的后果与说明二相同。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Lock</span> lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XxxLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// ...</span>
lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
	<span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">doOthers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>
	lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Lock</span> lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XxxLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// ...</span>
<span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">//如果此处抛出异常，则直接执行finally代码块</span>
    <span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//无论加锁是否成功，finally代码块都会执行</span>
    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token function">doOthers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>
	lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在使用尝试机制来获取锁的方式中，进入业务代码块之前，必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。<br>说明：Lock对象的unlock方法在执行时，它会调用AQS的tryRelease方法(取决于具体实现类)，如果当前线程不持有锁，则抛出IllegalMonitorStateException异常。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Lock</span> lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XxxLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// ...</span>
<span class="token keyword">boolean</span> isLocked <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>isLocked<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
        <span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">doOthers</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>
    	lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 并发修改同一记录时，避免更新丢失，需要加锁。要么在应用层加锁，要么在缓存加锁，要么在数据库层使用乐观锁，使用version作为更新依据。<br>说明：如果每次访问冲突概率小于20%，推荐使用乐观锁，否则使用悲观锁。乐观锁的重试次数不得小于3次。</p>
</li>
<li><p><strong>【强制】</strong> 多线程并行处理定时任务时，Timer运行多个TimeTask时，只要其中之一没有捕获抛出的异常，其它任务便会自动终止运行，使用ScheduledExecutorService则没有这个问题。</p>
</li>
<li><p>【推荐】资金相关的金融敏感信息，使用悲观锁策略。<br>说明：乐观锁在获得锁的同时已经完成了更新操作，校验逻辑容易出现漏洞，另外，乐观锁对冲突的解决策略有较复杂的要求，处理不当容易造成系统压力或数据异常，所以资金相关的金融敏感信息不建议使用乐观锁更新。<br>正例：悲观锁遵循一锁二判三更新四释放的原则</p>
</li>
<li><p>【推荐】使用CountDownLatch进行异步转同步操作，每个线程退出前必须调用countDown方法，线程执行代码注意catch异常，确保countDown方法被执行到，避免主线程无法执行至await方法，直到超时才返回结果。<br>说明：注意，子线程抛出异常堆栈，不能在主线程try-catch到。</p>
</li>
<li><p>【推荐】避免Random实例被多线程使用，虽然共享该实例是线程安全的，但会因竞争同一seed导致的性能下降。<br>说明：Random实例包括java.util.Random的实例或者Math.random()的方式。<br>正例：在JDK7之后，可以直接使用API ThreadLocalRandom，而在JDK7之前，需要编码保证每个线程持有一个单独的Random实例。</p>
</li>
<li><p>【推荐】通过双重检查锁(double-checked locking)(在并发场景下)实现延迟初始化的优化问题隐患(可参考The “Double-Checked Locking is Broken” Declaration)，推荐解决方案中较简单一种(适用于JDK5及以上版本)，将目标属性声明为volatile型(比如修改helper的属性声明为’private volatile Helper helper &#x3D; null;’)。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LazylnitDemo</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">private</span> <span class="token class-name">Helper</span> helper <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token class-name">Helper</span> <span class="token function">getHelper</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>helper <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>helper <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> 
                    helper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Helper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">&#125;</span>
            <span class="token punctuation">&#125;</span>
        <span class="token punctuation">&#125;</span>
        <span class="token keyword">return</span> helper<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token comment">// other methods and fields...</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【参考】volatile解决多线程内存不可见问题。对于一写多读，是可以解决变量同步问题，但是如果多写，同样无法解决线程安全问题。<br>说明：如果是count++操作，使用如下类实现：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Atomiclnteger</span> count <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Atomiclnteger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
count<span class="token punctuation">.</span><span class="token function">addAndGet</span><span class="token punctuation">(</span>l<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p>如果是JDK8，推荐使用LongAdder对象，比AtomicLong性能更好(减少乐观锁的重试次数)。</p>
</li>
<li><p>【参考】HashMap在容量不够进行resize时由于高并发可能出现死链，导致CPU飙升，在开发过程中注意规避此风险。</p>
</li>
<li><p>【参考】ThreadLocaI对象使用static修饰，ThreadLocal无法解决共享对象的更新问题。<br>说明：这个变量是针对一个线程内所有操作共享的，所以设置为静态变量，所有此类实例共享此静态变量，也就是说在类第一次被使用时装载，只分配一块存储空间，所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。</p>
</li>
</ol>
<h3 id="八-控制语句"><a href="#八-控制语句" class="headerlink" title="(八) 控制语句"></a>(八) 控制语句</h3><ol>
<li><p><strong>【强制】</strong> 在一个switch块内，每个case要么通过continue&#x2F;break&#x2F;return等来终止，要么注释说明程序将继续执行到哪一个case为止；在一个switch块内，都必须包含一个default语句并且放在最后，即使它什么代码也没有。<br>说明：注意break是退出switch语句块，而return是退出方法体。</p>
</li>
<li><p><strong>【强制】</strong> 当switch括号内的变量类型为String并且此变量为外部参数时，必须先进行null判断。<br>反例：如下的代码输出是什么？</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Switchstring</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    	<span class="token function">method</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">method</span><span class="token punctuation">(</span><span class="token class-name">String</span> param<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">switch</span> <span class="token punctuation">(</span>param<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">//肯定不是进入这里</span>
        <span class="token keyword">case</span> <span class="token string">"sth"</span><span class="token operator">:</span>
            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"it's sth"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">break</span><span class="token punctuation">;</span>
        <span class="token comment">//也不是进入这里</span>
        <span class="token keyword">case</span> <span class="token string">"null"</span><span class="token operator">:</span>
            <span class="token class-name">System</span> <span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"it's null"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">break</span><span class="token punctuation">;</span>
        <span class="token comment">//也不是进入这里</span>
        <span class="token keyword">default</span><span class="token operator">:</span>
        	<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"default"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在if&#x2F;else&#x2F;for&#x2F;while&#x2F;do语句中必须使用大括号。<br>说明：即使只有一行代码，禁止不采用大括号的编码方式：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">if</span> <span class="token punctuation">(</span>condition<span class="token punctuation">)</span> statements<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 三目运算符condition?表达式1:表达式2中，高度注意表达式1和2在类型对齐时，可能抛出因自动拆箱导致的NPE异常。<br>说明：以下两种场景会触发类型对齐的拆箱操作：<br>1)表达式1或表达式2的值只要有一个是原始类型。<br>2)表达式1或表达式2的值的类型不一致，会强制拆箱升级成表示范围更大的那个类型。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Integer</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token class-name">Integer</span> b <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>
<span class="token class-name">Integer</span> c <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token class-name">Boolean</span> flag <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token comment">// a*b的结果是int类型，那么c会强制拆箱成int类型，抛出NPE异常</span>
<span class="token class-name">Integer</span> result<span class="token operator">=</span><span class="token punctuation">(</span>flag <span class="token operator">?</span> a<span class="token operator">*</span>b <span class="token operator">:</span> c<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 在高并发场景中，避免使用”等于”判断作为中断或退出的条件。<br>说明：如果并发控制没有处理好，容易产生等值判断被”击穿”的情况，使用大于或小于的区间判断条件来代替。<br>反例：判断剩余奖品数量等于0时，终止发放奖品，但因为并发处理错误导致奖品数量瞬间变成了负数，这样的话，活动无法终止。</p>
</li>
<li><p>【推荐】当某个方法的代码行数超过10行时，return&#x2F;throw等中断逻辑的右大括号后加一个空行。<br>说明：这样做逻辑清晰，有利于代码阅读时重点关注。</p>
</li>
<li><p>【推荐】表达异常的分支时，少用if-else方式，这种方式可以改写成：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">if</span> <span class="token punctuation">(</span>condition<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">return</span> obj<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>
<span class="token comment">//接着写else的业务逻辑代码；</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>说明：如果非使用if()…else if()…else…方式表达逻辑，避免后续代码维护困难，请勿超过3层。<br>正例：超过3层的if-else的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现，其中卫语句示例如下:</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> findBoyfriend <span class="token punctuation">(</span><span class="token class-name">Man</span> man<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>man<span class="token punctuation">.</span><span class="token function">isUgly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"本姑娘是外貌协会的资深会员"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>man<span class="token punctuation">.</span><span class="token function">isPoor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"贫贱夫妻百事哀"</span><span class="token punctuation">)</span>；
        <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>man<span class="token punctuation">.</span><span class="token function">isBadTemper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"银河有多远，你就给我滚多远"</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"可以先交往一段时间看看"</span><span class="token punctuation">)</span>；

<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】除常用方法(如getXxx&#x2F;isXxx)等外，不要在条件判断中执行其它复杂的语句，将复杂逻辑判断的结果赋值给一个有意义的布尔变量名，以提高可读性。<br>说明：很多if语句内的逻辑表达式相当复杂，与、或、取反混合运算，甚至各种方法纵深调用，理解成本非常高。如果赋值一个非常好理解的布尔变量名字，则是件令人爽心悦目的事情。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//伪代码如下</span>
<span class="token keyword">final</span> <span class="token keyword">boolean</span> existed <span class="token operator">=</span> <span class="token punctuation">(</span>file<span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span>fileName<span class="token punctuation">,</span> <span class="token string">"w"</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>existed<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
<span class="token punctuation">&#125;</span>	<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> acquire <span class="token punctuation">(</span><span class="token keyword">long</span> arg<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token function">addWaiter</span><span class="token punctuation">(</span><span class="token class-name">Node</span><span class="token punctuation">.</span><span class="token constant">EXCLUSIVE</span><span class="token punctuation">)</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    	<span class="token function">selflnterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】不要在其它表达式（尤其是条件表达式）中，插入赋值语句。<br>说明：赋值点类似于人体的穴位，对于代码的理解至关重要，所以赋值语句需要清晰地单独成为一行。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token class-name">Lock</span> <span class="token function">getLock</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> fair<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">//算术表达式中出现赋值操作，容易忽略count值已经被改变</span>
    threshold <span class="token operator">=</span> count <span class="token operator">=</span> <span class="token class-name">Integer</span> <span class="token constant">MAX_VALUE</span><span class="token operator">|</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token comment">//条件表达式中出现赋值操作，容易误认为是sync==fair</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span>sync <span class="token operator">=</span> fair<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">FairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">NonfairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>【推荐】循环体中的语句要考量性能，以下操作尽量移至循环体外处理，如定义对象、变量、获取数据库连接，进行不必要的try-catch操作（这个try-catch是否可以移至循环体外）。</p>
</li>
<li><p>【推荐】避免采用取反逻辑运算符。<br>说明：取反逻辑不利于快速理解，并且取反逻辑写法必然存在对应的正向逻辑写法。<br>正例：使用if (x &lt; 628)来表达x小于628。<br>反例：使用if (!(x &gt;&#x3D; 628))来表达x小于628。</p>
</li>
<li><p>【推荐】接口入参保护，这种场景常见的是用作批量操作的接口。<br>反例：某业务系统，提供一个用户批量查询的接口，API文档上有说最多查多少个，但接口实现上没做任何保护，导致调用方传了一个1000的用户id数组过来后，查询信息后，内存爆了。</p>
</li>
<li><p>【参考】下列情形，需要进行参数校验：<br>1)调用频次低的方法。<br>2)执行时间开销很大的方法。此情形中，参数校验时间几乎可以忽略不计，但如果因为参数错误导致中间执行回退，或者错误，那得不偿失。<br>3)需要极高稳定性和可用性的方法。<br>4)对外提供的开放接口，不管是RPC&#x2F;API&#x2F;HTTP接口。<br>5)敏感权限入口。</p>
</li>
<li><p>【参考】下列情形，不需要进行参数校验：<br>1)极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查。<br>2)底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道，参数错误不太可能到底层才会暴露问题。一般DAO层与Service层都在同一个应用中，部署在同一台服务器中，所以DAO的参数校验，可以省略。<br>3)被声明成private只会被自己代码所调用的方法，如果能瓣确定调用方法的代码传入参数已经做过检查或者肯定不会有问题，此时可以不校验参数。</p>
</li>
</ol>
<h3 id="九-注释规约"><a href="#九-注释规约" class="headerlink" title="(九) 注释规约"></a>(九) 注释规约</h3><ol>
<li><p><strong>【强制】</strong> 类、类属性、类方法的注释必须使用Javadoc规范，使用&#x2F;**内容*&#x2F;格式，不得使用&#x2F;&#x2F;xxx方式。<br>说明：在IDE编辑窗口中，Javadoc方式会提示相关注释，生成Javadoc可以正确输出相应注释；在IDE中，工程调用方法时，不进入方法即可悬浮提示方法、参数、返回值的意义，提高阅读效率。</p>
</li>
<li><p><strong>【强制】</strong> 所有的抽象方法（包括接口中的方法）必须要用Javadoc注释、除了返回值、参数、异常说明外，还必须指出该方法做什么事情，实现什么功能。<br>说明：对子类的实现要求，或者调用注意事项，请一并说明。</p>
</li>
<li><p><strong>【强制】</strong> 所有的类都必须添加创建者和创建日期。<br>说明：在设置模板时，注意IDEA的@author为’${USER}’,而eclipse的@author为’${user}’,大小写有区别，而日期的设置统一为yyyy&#x2F;MM&#x2F;dd的格式。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">/**
  * @author yangguanbao
  * @date 2016/10/31
  */</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 方法内部单行注释，在被注释语句上方另起一行，使用&#x2F;&#x2F;注释。方法内部多行注释使用&#x2F;* *&#x2F;注释，注意与代码对齐。</p>
</li>
<li><p><strong>【强制】</strong> 所有的枚举类型字段必须要有注释，说明每个数据项的用途。</p>
</li>
<li><p>【推荐】与其”半吊子”英文来注释，不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。<br>反例：“TCP连接超时”解释成”传输控制协议连接超时”，理解反而费脑筋。</p>
</li>
<li><p>【推荐】代码修改的同时，注释也要进行相应的修改，尤其是参数、返回值、异常、核心逻辑等的修改。<br>说明：代码与注释更新不同步，就像路网与导航软件更新不同步一样，如果导航软件严重滞后，就失去了导航的意义。</p>
</li>
<li><p>【推荐】在类中删除未使用的任何字段和方法；在方法中删除未使用的任何参数声明与内部变量。</p>
</li>
<li><p>【参考】谨慎注释掉代码。在上方详细说明，而不是简单地注释掉。如果无用，则删除。<br>说明：代码被注释掉有两种可能性：1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉即可，假如需要查阅历史代码，登录代码仓库即可。</p>
</li>
<li><p>【参考】对于注释的要求：第一、能够准确反映设计思想和代码逻辑；第二、能够描述业务含义，使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书，注释是给自己看的，即使隔很长时间，也能清晰理解当时的思路；注释也是给继任者看的，使其能够快速接替自己的工作。</p>
</li>
<li><p>【参考】好的命名、代码结构是自解释的，注释力求精简准确、表达到位。避免出现注释的一个极端：过多过滥的注释，代码的逻辑一旦修改，修改注释是相当大的负担。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// put elephant into fridge</span>
<span class="token function">put</span><span class="token punctuation">(</span>elephant<span class="token punctuation">,</span> fridge<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p>方法名put，加上两个有意义的变量名elephant和fridge，已经说明了这是在干什么，语义清晰的代码不需要额外的注释。</p>
</li>
<li><p>【参考】特殊注释标记，请注明标记人与标记时间。注意及时处理这些标记，通过标记扫描，经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。<br>1)待办事宜（<strong>TODO</strong>）:（标记人，标记时间，［预计处理时间］）<br>表示需要实现，但目前还未实现的功能。这实际上是一个Javadoc的标签，目前的Javadoc还没有实现，但已经被广泛使用。只能应用于类，接口和方法（因为它是一个Javadoc标签）。<br>2)错误，不能工作（<strong>FIXME</strong>）:（标记人，标记时间，［预计处理时间］）<br>在注释中用FIXME标记某代码是错误的，而且不能工作，需要及时纠正的情况。</p>
</li>
</ol>
<h3 id="十-其它"><a href="#十-其它" class="headerlink" title="(十) 其它"></a>(十) 其它</h3><ol>
<li><p><strong>【强制】</strong> 在使用正则表达式时，利用好其预编译功能，可以有效加快正则匹配速度。<br>说明：不要在方法体内定义：Pattern pattern &#x3D; Pattern.compile(“规则”);</p>
</li>
<li><p><strong>【强制】</strong> 避免用Apache Beanutils进行属性的copy。<br>说明：Apache BeanUtils性能较差，可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier，注意均是浅拷贝。</p>
</li>
<li><p><strong>【强制】</strong> velocity调用POJO类的属性时，直接使用属性名取值即可，模板引擎会自动按规范调用POJO的getXxx()，如果是boolean基本数据类型变量（boolean命名不需要加is前缀），会自动调用isXxx()方法。<br>说明：注意如果是Boolean包装类对象，优先调用getXxx()的方法。</p>
</li>
<li><p><strong>【强制】</strong> 后台输送给页面的变量必须加$!{var}中间的感叹号。<br>说明：如果var等于null或者不存在，那么${var}会直接显示在页面上。</p>
</li>
<li><p><strong>【强制】</strong> 注意Math.random()这个方法返回是double类型，注意取值的范围0&lt;&#x3D;x&lt;1（能够取到<strong>零</strong>值，注意除零异常），如果想获取整数类型的随机数，不要将x放大10的若干倍然后取整，直接使用Random对象的<strong>nextInt</strong>或者<strong>nextLong</strong>方法。</p>
</li>
<li><p>【推荐】不要在视图模板中加入任何复杂的逻辑。<br>说明：根据MVC理论，视图的职责是展示，不要抢模型和控制器的活。</p>
</li>
<li><p>【推荐】任何数据结构的构造或初始化，都应指定大小，避免数据结构无限增长吃光内存。</p>
</li>
<li><p>【推荐】及时清理不再使用的代码段或配置信息。<br>说明：对于垃圾代码或过时配置，坚决清理干净，避免程序过度臃肿，代码冗余。<br>正例：对于暂时被注释掉，后续可能恢复使用的代码片断，在注释代码上方，统一规定使用三个斜杠(&#x2F;&#x2F;&#x2F;)来说明注释掉代码的理由。如：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
    <span class="token comment">///业务方通知活动暂停</span>
    <span class="token comment">// Business business = new Business();</span>
    <span class="token comment">// business.active();</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span> out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"it's finished"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li>
</ol>
<h2 id="二、异常日志"><a href="#二、异常日志" class="headerlink" title="二、异常日志"></a>二、异常日志</h2><h3 id="一-错误码"><a href="#一-错误码" class="headerlink" title="(一) 错误码"></a>(一) 错误码</h3><ol>
<li><p><strong>【强制】</strong> 错误码的制定原则：快速溯源、简单易记、沟通标准化。<br>说明：错误码想得过于完美和复杂，就像康熙字典中的生僻字一样，用词似乎精准，但是字典不容易随身携带并且简单易懂。<br>正例：错误码回答的问题是谁的错？错在哪？1)错误码必须能够快速知晓错误来源，可快速判断是谁的问题。2)错误码易于记忆和比对（代码中容易equals）。3)错误码能够脱离文档和系统平台达到线下轻量化地自由沟通的目的。</p>
</li>
<li><p><strong>【强制】</strong> 错误码不体现版本号和错误等级信息。<br>说明：错误码以不断追加的方式进行兼容。错误等级由日志和错误码本身的释义来决定。</p>
</li>
<li><p><strong>【强制】</strong> 全部正常，但不得不填充错误码时返回五个零：00000。</p>
</li>
<li><p><strong>【强制】</strong> 错误码为字符串类型，共5位，分成两个部分：错误产生来源+四位数字编号。<br>说明：错误产生来源分为A&#x2F;B&#x2F;C，A表示错误来源于用户，比如参数错误，用户安装版本过低，用户支付超时等问题；B表示错误来源于当前系统，往往是业务逻辑出错，或程序健壮性差等问题；C表示错误来源于第三方服务，比如CDN服务出错，消息投递超时等问题；四位数字编号从0001到9999，大类之间的步长间距预留100</p>
</li>
<li><p><strong>【强制】</strong> 编号不与公司业务架构，更不与组织架构挂钩，一切与平台先到先申请的原则进行，审批生效，编号即被永久固定。</p>
</li>
<li><p><strong>【强制】</strong> 错误码使用者避免随意定义新的错误码。<br>说明：尽可能在原有错误码附表中找到语义相同或者相近的错误码在代码中使用即可。</p>
</li>
<li><p><strong>【强制】</strong> 错误码不能直接输出给用户作为提示信息使用。<br>说明：堆栈（stack_trace）、错误信息（error_message）、错误码（error_code）、提示信息（user_tip）是一个有效关联并互相转义的和谐整体，但是请勿互相越俎代庖。</p>
</li>
<li><p>【推荐】错误码之外的业务独特信息由error_message来承载，而不是让错误码本身涵盖过多具体业务属性。</p>
</li>
<li><p>【推荐】在获取第三方服务错误码时，向上抛出允许本系统转义，由C转为B，并且在错误信息上带上原有的第三方错误码。</p>
</li>
<li><p>【参考】错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。<br>说明：在无法更加具体确定的错误场景中，可以直接使用一级宏观错误码，分别是：A0001（用户端错误）、B0001（系统执行出错）、C0001（调用第三方服务出错）。<br>正例：调用第三方服务出错是一级，中间件错误是二级，消息月艮务出错是三级。</p>
</li>
<li><p>【参考】错误码的后三位编号与HTTP状态码没有任何关系。</p>
</li>
<li><p>【参考】错误码尽量有利于不同文化背景的开发者进行交流与代码协作。<br>说明：英文单词形式的错误码不利于非英语母语国家（如阿拉伯语、希伯来语、俄罗斯语等）之间的开发者互相协作。</p>
</li>
<li><p>【参考】错误码即人性，感性认知+口口相传，使用纯数字来进行错误码编排不利于感性记忆和分类。<br>说明：数字是一个整体，每位数字的地位和含义是相同的。<br>反例：一个五位数字12345，第1位是错误等级，第2位是错误来源，345是编号，人的大脑不会主动地分辨每位数字的不同含义。</p>
</li>
</ol>
<h3 id="二-异常处理"><a href="#二-异常处理" class="headerlink" title="(二) 异常处理"></a>(二) 异常处理</h3><ol>
<li><p><strong>【强制】</strong> Java类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch的方式来处理，比如：NullPointerException，IndexOutOfBoundsException等等。<br>说明：无法通过预检查的异常除外，比如，在解析字符串形式的数字时，可能存在数字格式错误，不得不通过catch NumberFormatException来实现。<br>正例:<code>if (obj != null) &#123;...&#125;</code><br>反例:<code>try &#123; obj.method();&#125; catch(NullPointerException e) &#123;...&#125;</code></p>
</li>
<li><p><strong>【强制】</strong> 异常不要用来做流程控制，条件控制。<br>说明：异常设计的初衷是解决程序运行中的各种意外情况，且异常的处理效率比条件判断方式要低很多。</p>
</li>
<li><p><strong>【强制】</strong> catch时请分清稳定代码和非稳定代码，稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型，再做对应的异常处理。<br>说明：对大段代码进行try-catch，使程序无法根据不同的异常做出正确的应激反应，也不利于定位问题，这是一种不负责任的表现。<br>正例：用户注册的场景中，如果用户输入非法字符，或用户名称已存在，或用户输入密码过于简单，在程序上作出分门别类的判断，并提示给用户。</p>
</li>
<li><p><strong>【强制】</strong> 捕获异常是为了处理它，不要捕获了却什么都不处理而抛弃之，如果不想处理它，请将该异常抛给它的调用者。最外层的业务使用者，必须处理异常，将其转化为用户可以理解的内容。</p>
</li>
<li><p><strong>【强制】</strong> 事务场景中，抛出异常被catch后，如果需要回滚，一定要注意手动回滚事务。</p>
</li>
<li><p><strong>【强制】</strong> finally块必须对资源对象、流对象进行关闭，有异常也要做try-catch。<br>说明：如果JDK7及以上，可以使用try-with-resources方式。</p>
</li>
<li><p><strong>【强制】</strong> 不要在finally块中使用return。<br>说明：try块中的return语句执行成功后，并不马上返回，而是继续执行finally块中的语句，如果此处存在return语句，则在此直接返回，无情丢弃掉try块中的返回点。<br>反例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">checkReturn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">// x等于1，此处不返回</span>
        <span class="token keyword">return</span> <span class="token operator">++</span>x<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>
        <span class="token comment">//返回的结果是2</span>
        <span class="token keyword">return</span> <span class="token operator">++</span>x<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 捕获异常与抛异常，必须是完全匹配，或者捕获异常是抛异常的父类。<br>说明：如果预期对方抛的是绣球，实际接到的是铅球，就会产生意外情况。</p>
</li>
<li><p><strong>【强制】</strong> 在调用RPC、二方包、或动态生成类的相关方法时，捕捉异常必须使用Throwable类来进行拦截。<br>说明：通过反射机制来调用方法，如果找不到方法，抛出NoSuchMethodException。什么情况会抛出NoSuchMethodError呢？二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配，或者在字节码修改框架（比如：ASM）动态创建或修改类时，修改了相应的方法签名。这些情况，即使代码编译期是正确的，但在代码运行期时，会抛出NoSuchMethodError。</p>
</li>
<li><p>【推荐】方法的返回值可以为null，不强制返回空集合，或者空对象等，必须添加注释充分说明什么情况下会返回null值。<br>说明：本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象，对调用者来说，也并非高枕无忧，必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。</p>
</li>
<li><p>【推荐】防止NPE，是程序员的基本修养，注意NPE产生的场景：<br>1)返回类型为基本数据类型，return包装数据类型的对象时，自动拆箱有可能产生NPE。<br>反例：public int f() { return Integer对象}，如果为null，自动解箱抛NPE。<br>2)数据库的查询结果可能为null。<br>3)集合里的元素即使isNotEmpty，取出的数据元素也可能为null。<br>4)远程调用返回对象时，一律要求进行空指针判断，防止NPE。<br>5)对于Session中获取的数据，建议进行NPE检查，避免空指针。<br>6)级联调用obj.getA().getB().getC();一连串调用，易产生NPE。<br>正例：使用JDK8的Optional类来防止NPE问题。</p>
</li>
<li><p>【推荐】定义时区分unchecked&#x2F;checked异常，避免直接抛出new RuntimeException(),更不允许抛出Exception或者Throwable，应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常，如：DAOException&#x2F;ServiceException等。</p>
</li>
<li><p>【参考】对于公司外的http&#x2F;api开放接口必须使用”错误码”；而应用内部推荐异常抛出；跨应用间RPC调用优先考虑使用Result方式，封装isSuccess()方法、”错误码”、”错误简短信息”；而应用内部推荐异常抛出。<br>说明：关于RPC方法返回方式使用Result方式的理由：<br>1)使用抛异常返回方式，调用方如果没有捕获到就会产生运行时错误。<br>2)如果不加栈信息，只是new自定义异常，加入自己的理解的error message，对于调用端解决问题的帮助不会太多。如果加了栈信息，在频繁调用出错的情况下，数据序列化和传输的性能损耗也是问题。</p>
</li>
<li><p>【参考】避免出现重复的代码(Don’t Repeat Yourself)，即DRY原则。<br>说明：随意复制和粘贴代码，必然会导致代码的重复，在以后需要修改时，需要修改所有的副本，容易遗漏。必要时抽取共性方法，或者抽象公共类，甚至是组件化。<br>正例：一个类中有多个public方法，都需要进行数行相同的参数校验操作，这个时候请抽取：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">checkParam</span><span class="token punctuation">(</span><span class="token class-name">DTO</span> dto<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li>
</ol>
<h3 id="三-日志规约"><a href="#三-日志规约" class="headerlink" title="(三) 日志规约"></a>(三) 日志规约</h3><ol>
<li><p><strong>【强制】</strong> 应用中不可直接使用日志系统(Log4js Logback)中的API，而应依赖使用日志框架(SLF4J、JCL–Jakarta Commons Logging)中的API，使用门面模式的日志框架，有利于维护和各个类的日志处理方式统一。<br>说明：日志框架(SLF4J、JCL–Jakarta Commons Logging)的使用方式(推荐使用SLF4J)<br>使用SLF4J:</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>slf4j<span class="token punctuation">.</span></span><span class="token class-name">Logger</span></span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>slf4j<span class="token punctuation">.</span></span><span class="token class-name">LoggerFactory</span></span><span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">Logger</span> logger <span class="token operator">=</span> <span class="token class-name">LoggerFactory</span><span class="token punctuation">.</span><span class="token function">getLogger</span><span class="token punctuation">(</span><span class="token class-name">Test</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
<p>使用JCL:</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>commons<span class="token punctuation">.</span>logging<span class="token punctuation">.</span></span><span class="token class-name">Log</span></span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>commons<span class="token punctuation">.</span>logging<span class="token punctuation">.</span></span><span class="token class-name">LogFactory</span></span><span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">Log</span> log <span class="token operator">=</span> <span class="token class-name">LogFactory</span><span class="token punctuation">.</span><span class="token function">getLog</span><span class="token punctuation">(</span><span class="token class-name">Test</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 所有日志文件至少保存15天，因为有些异常具备以”周”为频次发生的特点。对于当天日志，以”应用名.log”来保存，保存在&#x2F;home&#x2F;admin&#x2F;应用名&#x2F;logs&#x2F;&lt;&#x2F;font&gt;目录下，过往日志格式为:{logname}.log.{保存日期}，日期格式：yyyy-MM-dd<br>说明：以mppserver应用为例，日志保存在&#x2F;home&#x2F;admin&#x2F;mppserver&#x2F;logs&#x2F;mppserver.log，历史日志名称为mppserver.log.2016-08-01</p>
</li>
<li><p><strong>【强制】</strong> 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式：appName_logType_logName.log。logType:日志类型，如stats&#x2F;monitor&#x2F;access等；logName:日志描述。这种命名的好处：通过文件名就可知道日志文件属于什么应用，什么类型，什么目的，也有利于归类查找。<br>说明：推荐对日志进行分类，如将错误日志和业务日志分开存放，便于开发人员查看，也便于通过日志对系统进行及时监控。<br>正例：mppserver应用中单独监控时区转换异常，如：mppserver_monitor_timeZoneConvert.log</p>
</li>
<li><p><strong>【强制】</strong> 在日志输出时，字符串变量之间的拼接使用占位符的方式。<br>说明：因为String字符串的拼接会使用StringBuilder的append()方式，有一定的性能损耗。使用占位符仅是替换动作，可以有效提升性能。<br>正例:logger.debug(“Processing trade with id: {} and symbol: {}”, id, symbol);</p>
</li>
<li><p><strong>【强制】</strong> 对于trace&#x2F;debug&#x2F;info级别的日志输出，必须进行日志级别的开关判断。<br>说明：虽然在debug(参数)的方法体内第一行代码isDisabled(Level.DEBUG」NT)为真时(Slf4j的常见实现Log4j和Logback)，就直接return，但是参数可能会进行字符串拼接运算。此外，如果debug(getName())这种参数内有getName()方法调用，无谓浪费方法调用的开销。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//如果判断为真，那么可以输出trace和debug级别的日志</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>logger<span class="token punctuation">.</span><span class="token function">isDebugEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
	logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Current ID is: &#123;&#125; and name is: &#123;&#125;"</span><span class="token punctuation">,</span> id<span class="token punctuation">,</span> getName。<span class="token punctuation">)</span>；
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 避免重复打印日志，浪费磁盘空间，务必在log4j.xml中设置additivity&#x3D;false。<br>正例：&lt; logger name&#x3D;”com.taobao.dubbo.config” additivity&#x3D;”false” &gt;</p>
</li>
<li><p><strong>【强制】</strong> 生产环境禁止直接使用System.out或System.err输出日志或使用e.printStackTrace()打印异常堆栈。<br>说明：标准日志输出与标准错误输出文件每次Jboss重启时才滚动，如果大量输出送往这两个文件，容易造成文件大小超过操作系统大小限制。</p>
</li>
<li><p><strong>【强制】</strong> 异常信息应该包括两类信息：案发现场信息和异常堆栈信息。如果不处理，那么通过关键字throws往上抛出。<br>正例:logger.error(各各类参数或者对象toStringO + “_“ + e.getMessage(), e);</p>
</li>
<li><p><strong>【强制】</strong> 日志打印时禁止直接用JSON工具将对象转换成String。<br>说明：如果对象里某些get方法被重写，存在抛出异常的情况，则可能会因为打印日志而影响正常业务流程的执行。<br>正例：打印日志时仅打印出业务相关属性值或者调用其对象的toString()方法。</p>
</li>
<li><p>【推荐】谨慎地记录日志。生产环境禁止输出debug日志；有选择地输出info日志；如果使用warn来记录刚上线时的业务行为信息，一定要注意日志输出量的问题，避免把服务器磁盘撑爆，并记得及时删除这些观察日志。<br>说明：大量地输出无效日志，不利于系统性能提升，也不利于快速定位错误点。记录日志时请思考：这些日志真的有人看吗？看到这条日志你能做什么？能不能给问题排查带来好处？</p>
</li>
<li><p>【推荐】可以使用warn日志级别来记录用户输入参数错误的情况，避免用户投诉时，无所适从。如非必要，请不要在此场景打出error级别，避免频繁报警。<br>说明：注意日志输出的级别，error级别只记录系统逻辑出错、异常或者重要的错误信息。</p>
</li>
<li><p>【推荐】尽量用英文来描述日志错误信息，如果日志中的错误信息用英文描述不清楚的话使用中文描述即可，否则容易产生歧义。<br>说明：国际化团队或海外吝隧的服务器由于字符集问题，使用全英文来注释和描述日志错误信息。</p>
</li>
</ol>
<h2 id="三、单元测试"><a href="#三、单元测试" class="headerlink" title="三、单元测试"></a>三、单元测试</h2><ol>
<li><strong>【强制】</strong> 好的单元测试必须遵守AIR原则。<br>说明：单元测试在线上运行时，感觉像空气（AIR）一样并不存在，但在测试质量的保障上，却是非常关键的。好的单元测试宏观上来说，具有自动化、独立性、可重复执行的特点。</li>
</ol>
<ul>
<li><strong>A</strong> : Automatic（自动化）</li>
<li><strong>I</strong> : Independent（独立性）</li>
<li><strong>R</strong> : Repeatable（可重复）</li>
</ul>
<ol start="2">
<li><p><strong>【强制】</strong> 单元测试应该是全自动执行的，并且非交互式的。测试用例通常是被定期执行的，执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.out来进行人肉验证，必须使用assert来验证。</p>
</li>
<li><p><strong>【强制】</strong> 保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护，单元测试用例之间决不能互相调用，也不能依赖执行的先后次序。<br>反例：method2需要依赖method1的执行，将执行结果作为method2的输入。</p>
</li>
<li><p><strong>【强制】</strong> 单元测试是可以重复执行的，不能受到外界环境的影响。<br>说明：单元测试通常会被放到持续集成中，每次有代码check in时单元测试都会被执行。如果单测对外部环境（网络、月服务、中间件等）有依赖，容易导致持续集成机制的不可用。<br>正例：为了不受外界环境影响，要求设计代码时就把SUT的依赖改成注入，在测试时用spring这样的DI框架注入一个本地（内存）实现或者Mock实现。</p>
</li>
<li><p><strong>【强制】</strong> 对于单元测试，要保证测试粒度足够小，有助于精确定位问题。单测粒度至多是类级U,—般是方法级别。<br>说明：只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑，那是集成测试的领域。</p>
</li>
<li><p><strong>【强制】</strong> 核心业务、核心应用、核心模块的增量代码确保单元测试通过。<br>说明：新增代码及时补充单元测试，如果新增代码影响了原有单元测试，请及时修正。</p>
</li>
<li><p><strong>【强制】</strong> 单元测试代码必须写在如下工程目录：src&#x2F;test&#x2F;java，不允许写在业务代码目录下。说明：源码编译时会跳过此目录，而单元测试框架默认是扫描此目录。</p>
</li>
<li><p>【推荐】单元测试的基本目标：语句覆盖率达到70%；核心模块的语句覆盖率和分支覆盖率都要达到100%<br>说明：在工程规约的应用分层中提到的DAO层，Manager层，可重用度高的Service，都应该进行单元测试。</p>
</li>
<li><p>【推荐】编写单元测试代码遵守BCDE原则，以保证被测试模块的交付质量。</p>
</li>
</ol>
<ul>
<li><strong>B</strong> : Border，边界值测试，包括循环边界、特殊取值、特殊时间点、数据顺序等。</li>
<li><strong>C</strong> : Correct，正确的输入，并得到预期的结果。</li>
<li><strong>D</strong> : Design，与设计文档相结合，来编写单元测试。</li>
<li><strong>E</strong> : Error，强制错误信息输入（如：非法数据、异常流程、业务允许外等），并得到预期的结果。</li>
</ul>
<ol start="10">
<li><p>【推荐】对于数据库相关的查询，更新，删除等操作，不能假设数据库里的数据是存在的，或者直接操作数据库把数据插入进去，请使用程序插入或者导入数据的方式来准备数据。<br>反例：删除某一行数据的单元测试，在数据库中，先直接手动增加一行作为删除目标，但是这一行新增数据并不符合业务插入规则，导致测试结果异常。</p>
</li>
<li><p>【推荐】和数据库相关的单元测试，可以设定自动回滚机制，不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。<br>正例：在阿里巴巴企业智能事业部的内部单元测试中，使用<strong>ENTERPRISE_INTELLIGENCE_UNIT_TEST</strong>的前缀来标识单元测试相关代码。</p>
</li>
<li><p>【推荐】对于不可测的代码在适当的时机做必要的重构，使代码变得可测，避免为了达到测试要求而书写不规范测试代码。</p>
</li>
<li><p>【推荐】在设计评审阶段，开发人员需要和测试人员一起确定单元测试范围，单元测试最好覆盖所有测试用例（UC）。</p>
</li>
<li><p>【推荐】单元测试作为一种质量保障手段，在项目提测前完成单元测试，不建议项目发布后补充单元测试用例。</p>
</li>
<li><p>【参考】为了更方便地进行单元测试，业务代码应避免以下情况：</p>
</li>
</ol>
<ul>
<li>构造方法中做的事情过多。</li>
<li>存在过多的全局变量和静态方法。</li>
<li>存在过多的外部依赖。</li>
<li>存在过多的条件语句。<br>说明：多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。</li>
</ul>
<ol start="16">
<li>【参考】不要对单元测试存在如下误解：</li>
</ol>
<ul>
<li>那是测试同学干的事情。本文是开发手册，凡是本文内容都是与开发同学强相关的。</li>
<li>单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的。</li>
<li>单元测试代码不需要维护。一年半载后，那么单元测试几乎处于废弃状态。</li>
<li>单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。</li>
</ul>
<h2 id="四、安全规约"><a href="#四、安全规约" class="headerlink" title="四、安全规约"></a>四、安全规约</h2><ol>
<li><p><strong>【强制】</strong> 隶属于用户个人的页面或者功能必须进行权限控制校验。<br>说明：防止没有做水平权限校验就可随意访问、修改、删除别人的数据，比如查看他人的私信内容。</p>
</li>
<li><p><strong>【强制】</strong> 用户敏感数据禁止直接展示，必须对展示数据进行脱敏。<br>说明：中国大陆个人手机号码显示为:137****0969，隐藏中间4位，防止隐私泄露。</p>
</li>
<li><p><strong>【强制】</strong> 用户输入的SQL参数严格使用参数绑定或者METADATA字段值限定，防止SQL注入,禁止字符串拼接SQL访问数据库。<br>反例：某系统签名大量被恶意修改，即是因为对于危险字符#–没有进行转义，导致数据库更新时，where后边的信息被注释掉，对全库进行更新。</p>
</li>
<li><p><strong>【强制】</strong> 用户请求传入的任何参数必须做有效性验证。<br>说明：忽略参数校验可能导致：</p>
</li>
</ol>
<ul>
<li>page size过大导致内存溢出</li>
<li>恶意order by导致数据库慢查询</li>
<li>缓存击穿</li>
<li>SSRF</li>
<li>任意重定向</li>
<li>SQL注入，Shell注入，反序列化注入</li>
<li>正则输入源串拒绝服务ReDoS<br>Java代码用正则来验证客户端的输入，有些正则写法验证普通用户输入没有问题，但是如果攻击人员使用的是特殊构造的字符串来验证，有可能导致死循环的结果。</li>
</ul>
<ol start="5">
<li><p><strong>【强制】</strong> 禁止向HTML页面输出未经安全过滤或未正确转义的用户数据。</p>
</li>
<li><p><strong>【强制】</strong> 表单、AJAX提交必须执行CSRF安全验证。<br>说明：CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在CSRF漏洞的应用&#x2F;网站，攻击者可以事先构造好URL,只要受害者用户一访问，后台便在用户不知情的情况下对数据库中用户参数进行相应修改。</p>
</li>
<li><p><strong>【强制】</strong> URL外部重定向传入的目标地址必须执行白名单过滤。</p>
</li>
<li><p><strong>【强制】</strong> 在使用平台资源，譬如短信、邮件、电话、下单、支付，必须实现正确的防重放的机制，如数量限制、疲劳度控制、验证码校验，避免被滥刷而导致资损。<br>说明：如注册时发送验证码到手机，如果没有限制次数和频率，那么可以利用此功能骚扰到其它用户，并造成短信平台资源浪费。</p>
</li>
<li><p>【推荐】发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。</p>
</li>
</ol>
<h2 id="五、MySQL数据库"><a href="#五、MySQL数据库" class="headerlink" title="五、MySQL数据库"></a>五、MySQL数据库</h2><h3 id="一-建表规约"><a href="#一-建表规约" class="headerlink" title="(一) 建表规约"></a>(一) 建表规约</h3><ol>
<li><p><strong>【强制】</strong> 表达是与否概念的字段，必须使用is_xxx的方式命名，数据类型是unsigned tinyint（1表示是，0表示否）。<br>说明：任何字段如果为非负数，必须是unsigned,<br>注意：POJO类中的任何布尔类型的变量，都不要加is前缀，所以，需要在&lt;resultMap&gt;设置从is_xxx到Xxx的映射关系。数据库表示是与否的值，使用tinyint类型，坚持is_xxx的命名方式是为了明确其取值含义与取值范围。<br>正例：表达逻辑删除的字段名is_deleted，1表示删除，0表示未删除。</p>
</li>
<li><p><strong>【强制】</strong> 表名、字段名必须使用小写字母或数字，禁止出现数字开头，禁止两个下划线中间只出现数字。数据库字段名的修改代价很大，因为无法进行预发布，所以字段名称需要慎重考虑。<br>说明：MySQL在Windows下不区分大小写，但在Linux下默认是区分大小写。因此，数据库名、表名、字段名，都不允许出现任何大写字母，避免节外生枝。<br>正例：aliyun_admin，rdc_config，level3_name<br>反例：AliyunAdmin，rdcConfig，level_3_name</p>
</li>
<li><p><strong>【强制】</strong> 表名不使用复数名词。<br>说明：表名应该仅仅表示表里面的实体内容，不应该表示实体数量，对应于DO类名也是单数形式，符合表达习惯。</p>
</li>
<li><p><strong>【强制】</strong> 禁用保留字，如desc、range、match、delayed等，请参考MySQL官方保留字。</p>
</li>
<li><p><strong>【强制】</strong> 主键索引名为pk-字段名；唯一索引名为uk_字段名;普通索引名则为idx_字段名。说明：pk_即primary key;uk_即unique key;idx_即index的简称。</p>
</li>
<li><p><strong>【强制】</strong> 小数类型为decimal，禁止使用float和double。<br>说明：在存储的时候，float和double都存在精度损失的问题，很可能在比较值的时候，得到不正确的结果。如果存储的数据范围超过decimal的范围，建议将数据拆成整数和小数并分开存储。</p>
</li>
<li><p><strong>【强制】</strong> 如果存储的字符串长度几乎相等，使用char定长字符串类型。</p>
</li>
<li><p><strong>【强制】</strong> varchar是可变长字符串，不预先分配存储空间，长度不要超过5000，如果存储长度大于此值，定义字段类型为text，独立出来一张表，用主键来对应，避免影响其它字段索引效率。</p>
</li>
<li><p><strong>【强制】</strong> 表必备三字段：id,gmt_create,gmt_modified。<br>说明：其中id必为主键，类型为bigint unsigned、单表时自增、步长为1。gmt_create,gmt_modified的类型均为datetime类型，前者现在时表示主动式创建，后者过去分词表示被动式更新。</p>
</li>
<li><p>【推荐】表的命名最好是遵循”业务名称一表的作用”。<br>正例：alipay_task&#x2F;force_project&#x2F;trade_config</p>
</li>
<li><p>【推荐】库名与应用名称尽量一致。</p>
</li>
<li><p>【推荐】如果修改字段含义或对字段表示的状态追加时，需要及时更新字段注释。</p>
</li>
<li><p>【推荐】字段允许适当冗余，以提高查询性能，但必须考虑数据一致。冗余字段应遵循：<br>1)不是频繁修改的字段。<br>2)不是唯一索引的字段。<br>3)不是varchar超长字段，更不能是text字段。<br>正例：各业务线经常冗余存储商品名称，避免查询时需要调用IC月服务获取。</p>
</li>
<li><p>【推荐】单表行数超过500万行或者单表容量超过2GB，才推荐进行分库分表。<br>说明：如果预计三年后的数据量根本达不到这个级别，请不要在创建表时就分库分表。</p>
</li>
<li><p>【参考】合适的字符存储长度，不但节约数据库表空间、节约索引存储，更重要的是提升检索速度。<br>正例：无符号值可以避免误存负数,且扩大了表示范围。</p>
</li>
</ol>
<table>
<thead>
<tr>
<th>对象</th>
<th>年龄区间</th>
<th>类型</th>
<th>字节</th>
<th>表示范围</th>
</tr>
</thead>
<tbody><tr>
<td>人</td>
<td>150岁之内</td>
<td>tinyint unsigned</td>
<td>1</td>
<td>无符号值：0到255</td>
</tr>
<tr>
<td>龟</td>
<td>数百岁</td>
<td>smallint unsigned</td>
<td>2</td>
<td>无符号值：0到65535</td>
</tr>
<tr>
<td>恐龙化石</td>
<td>数千万年</td>
<td>int unsigned</td>
<td>4</td>
<td>无符号值：0到约43亿</td>
</tr>
<tr>
<td>太阳</td>
<td>约50亿年</td>
<td>bigint unsigned</td>
<td>8</td>
<td>无符号值：0至约10的19次方</td>
</tr>
</tbody></table>
<h3 id="二-索引规约"><a href="#二-索引规约" class="headerlink" title="(二) 索引规约"></a>(二) 索引规约</h3><ol>
<li><p><strong>【强制】</strong> 业务上具有唯一特性的字段，即使是组合字段，也必须建成唯一索引。<br>说明：不要以为唯一索引影响了insert速度，这个速度损耗可以忽略，但提高查找速度是明显的；另外，即使在应用层做了非常完善的校验控制，只要没有唯一索引，根据墨菲定律，必然有脏数据产生。</p>
</li>
<li><p><strong>【强制】</strong> 超过三个表禁止join。需要join的字段，数据类型保持绝对一致；多表关联查询时，保证被关联的字段需要有索引。<br>说明：即使双表join也要注意表索引、SQL性能。</p>
</li>
<li><p><strong>【强制】</strong> 在varchar字段上建立索引时，必须指定索引长度，没必要对全字段建立索引，根据实际文本区分度决定索引长度。<br>说明：索引的长度与区分度是一对矛盾体，一般对字符串类型数据，长度为20的索引，区分度会高达90%以上，可以使用count(distinct left(列名，索引长度))&#x2F;count(*)的区分度来确定。</p>
</li>
<li><p><strong>【强制】</strong> 页面搜索严禁左模糊或者全模糊，如果需要请走搜索引擎来解决。<br>说明：索引文件具有B-Tree的最左前缀匹配特性，如果左边的值未确定，那么无法使用此索引。</p>
</li>
<li><p>【推荐】如果有order by的场景，请注意利用索引的有序性。order by最后的字段是组合索引的一部分，并且放在索引组合顺序的最后，避免出现file_sort的情况，影响查询性能。<br>正例：where a&#x3D;? and b&#x3D;? order by c;索引： a_b_c<br>反例：索引如果存在范围查询，那么索引有序性无法利用，如：WHERE a&gt;10 ORDER BY b;索引a_b无法排序。</p>
</li>
<li><p>【推荐】利用覆盖索引来进行查询操作，避免回表。<br>说明：如果一本书需要知道第11章是什么标题，会翻开第11章对应的那一页吗？目录浏览一下就好，这个目录就是起到覆盖索引的作用。<br>正例：能够建立索引的种类分为主键索引、唯一索引、普通索引三种，而覆盖索引只是一种查询的一种效果，用explain的结果，extra列会出现:using index。</p>
</li>
<li><p>【推荐】利用延迟关联或者子查询优化超多分页场景。<br>说明：MySQL并不是跳过offset行，而是取offset+N行，然后返回放弃前offset行，返回N行，那当offset特别大的时候，效率就非常的低下，要么控制返回的总页数，要么对超过特定阈值的页数进行SQL改写。<br>正例：先快速定位需要获取的id段，然后再关联：</p>
<pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">SELECT</span> a<span class="token punctuation">.</span><span class="token operator">*</span> <span class="token keyword">FROM</span> 表 <span class="token number">1</span> a<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">select</span> id <span class="token keyword">from</span> 表 <span class="token number">1</span> <span class="token keyword">where</span> 条件 <span class="token keyword">LIMIT</span> <span class="token number">100000</span><span class="token punctuation">,</span><span class="token number">20</span> <span class="token punctuation">)</span> b <span class="token keyword">where</span> a<span class="token punctuation">.</span>id<span class="token operator">=</span>b<span class="token punctuation">.</span>id<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>【推荐】SQL性能优化的目标：至少要达到range级别，要求是ref级别，如果可以是consts最好。<br>说明：<br>1)consts单表中最多只有一个匹配行(主键或者唯一索引)，在优化阶段即可读取到数据。<br>2)ref指的是使用普通的索引(normal index )。<br>3)range对索引进行范围检索。<br>反例：explain表的结果，type&#x3D;index，索引物理文件全扫描，速度非常慢，这个index级别比较range还低，与全表扫描是小巫见大巫。</p>
</li>
<li><p>【推荐】建组合索引的时候，区分度最高的在最左边。<br>正例：如果where a&#x3D;? and b&#x3D;?，a列的几乎接近于唯一值，那么只需要单建idx_a索引即可。<br>说明：存在非等号和等号混合判断条件时，在建索引时，请把等号条件的列前置。如：where c&gt;? and d&#x3D;? 那么即使c的区分度更高，也必须把d放在索引的最前列，即建立组合索引idx_d_c。</p>
</li>
<li><p>【推荐】防止因字段类型不同造成的隐式转换，导致索引失效。</p>
</li>
<li><p>【参考】创建索引时避免有如下极端误解：<br>1)索引宁滥勿缺。认为一个查询就需要建一个索引。<br>2)吝啬索引的创建。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。<br>3)抵制惟一索引。认为惟一索引一律需要在应用层通过”先查后插”方式解决。</p>
</li>
</ol>
<h3 id="三-SQL语句"><a href="#三-SQL语句" class="headerlink" title="(三) SQL语句"></a>(三) SQL语句</h3><ol>
<li><p><strong>【强制】</strong> 不要使用count(列名)或count(常量)来替代count(*)，count(*)是SQL92定义的标准统计行数的语法，跟数据库无关，跟NULL和非NULL无关。<br>说明：count(*)会统计值为NULL的行，而count(列名)不会统计此列为NULL值的行。</p>
</li>
<li><p><strong>【强制】</strong> count(distinct col)计算该列除NULL之外的不重复行数注意count(distinct col1, col2)如果其中一列全为NULL，那么即使另一列有不同的值，也返回为0。</p>
</li>
<li><p><strong>【强制】</strong> 当某一列的值全是NULL时，count(col)的返回结果为0，但sum(col)的返回结果为 NULL，因此使用sum()时需注意NPE问题。<br>正例：可以使用如下方式来避免sum的NPE问题：SELECT IFNULL(SUM(column), 0) FROM table;</p>
</li>
<li><p><strong>【强制】</strong> 使用<strong>ISNULL()</strong> 来判断是否为NULL值。<br>说明：NULL与任何值的直接比较都为NULL<br>1)<strong>NULL&lt;&gt;NULL</strong>的返回结果是NULL，而不是false<br>2)<strong>NULL&#x3D;NULL</strong>的返回结果是NULL，而不是true<br>3)<strong>NULL&lt;&gt;1</strong>的返回结果是NULL，而不是true<br>反例：在SQL语句中，如果在null前换行，影响可读性。select * from table where column1 is null and column3 is not null;而’ISNULL(co山mn)’是一个整体，简洁易懂。从性能数据上分析，’ISNULL(column)’ 执行效率更快一些。</p>
</li>
<li><p><strong>【强制】</strong> 代码中写分页查询逻辑时，若count为0应直接返回，避免执行后面的分页语句。</p>
</li>
<li><p><strong>【强制】</strong> 不得使用外键与级联，一切外键概念必须在应用层解决。<br>说明：(概念解释)学生表中的student_id是主键，那么成绩表中的student_id则为外键。如果更新学生表中的student_id，同时触发成绩表中的student_id更新，即为级联更新。外键与级联更新适用于单机低并发，不适合分布式、高并发集群；级联更新是强阻塞，存在数据库更新风暴的风险；外键影响数据库的插入速度。</p>
</li>
<li><p><strong>【强制】</strong> 禁止使用存储过程，存储过程难以调试和扩展，更没有移植性。</p>
</li>
<li><p><strong>【强制】</strong> 数据订正(特别是删除或修改记录操作)时，要先select，避免出现误删除，确认无误才能执行更新语句。</p>
</li>
<li><p><strong>【强制】</strong> 对于数据库中表记录的查询和变更，只要涉及多个表,都需要在列名前加表的别名（或表名）进行限定。<br>说明：对多表进行查询记录、更新记录、删除记录时，如果对操作列没有限定表的别名（或表名），并且操作列在多个表中存在时，就会抛异常。<br>正例:</p>
<pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">select</span> t1 <span class="token punctuation">.</span>name <span class="token keyword">from</span> table_first <span class="token keyword">as</span> t1 <span class="token punctuation">,</span> table_second <span class="token keyword">as</span> t2 <span class="token keyword">where</span> t1 <span class="token punctuation">.</span>id<span class="token operator">=</span>t2<span class="token punctuation">.</span>id<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>反例：在某业务中，由于多表关联查询语句没有加表的别名（或表名）的限制，正常运行两年后，最近在某个表中增加一个同名字段，在预发布环境做数据库变更后，线上查询语句出现出1052异常：Column ‘name’ in field list is ambiguous。</p>
</li>
<li><p>【推荐】SQL语句中表的别名前加as,并且以tl、t2、t3、…的顺序依次命名。<br>说明：1)别名可以是表的简称，或者是根据表出现的顺序，以t1、t2、t3的方式命名。2)别名前加as使别名更容易识别。<br>正例：</p>
<pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">select</span> t1 <span class="token punctuation">.</span>name <span class="token keyword">from</span> table_first <span class="token keyword">as</span> t1<span class="token punctuation">,</span> table_second <span class="token keyword">as</span> t2 <span class="token keyword">where</span> t1<span class="token punctuation">.</span>id<span class="token operator">=</span>t2<span class="token punctuation">.</span>id<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>【推荐】in操作能避免则避免，若实在避免不了，需要仔细评估in后边的集合元素数量，控制在1000个之内。</p>
</li>
<li><p>【参考】因国际化需要，所有的字符存储与表示，均采用utf8字符集，那么字符计数方法需要注意。<br>说明：</p>
<pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">SELECT</span> LENGTH<span class="token punctuation">(</span><span class="token string">"轻松工作"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">--返回为12</span>
<span class="token keyword">SELECT</span> CHARACTER_LENGTH<span class="token punctuation">(</span><span class="token string">"轻松工作"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">--返回为4</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p>如果需要存储表情，那么选择utf8mb4来进行存储，注意它与utf8编码的区别。</p>
</li>
<li><p>【参考】TRUNCATE TABLE比DELETE速度快，且使用的系统和事务日志资源少，但TRUNCATE无事务且不触发trigger，有可能造成事故，故不建议在开发代码中使用此语句。<br>说明：TRUNCATE TABLE在功能上与不带WHERE子句的DELETE语句相同。</p>
</li>
</ol>
<h3 id="四-ORM映射"><a href="#四-ORM映射" class="headerlink" title="(四) ORM映射"></a>(四) ORM映射</h3><ol>
<li><p><strong>【强制】</strong> 在表查询中，一律不要使用*作为查询的字段列表，需要哪些字段必须明确写明。<br>说明：1)增加查询分析器解析成本。2)增减字段容易与resultMap配置不一致。3)无用字段增加网络消耗，尤其是text类型的字段。</p>
</li>
<li><p><strong>【强制】</strong> POJO类的布尔属性不能加is，而数据库字段必须加is_，要求在resultMap中进行字段与属性之间的映射。<br>说明：参见定义POJO类以及数据库字段定义规定，在sql.xml增加映射，是必须的。</p>
</li>
<li><p><strong>【强制】</strong> 不要用resultclass当返回参数，即使所有类属性名与数据库字段一一对应，也需要定义&lt;resultMap&gt;;反过来，每一个表也必然有一个&lt;resultMap&gt;与之对应。<br>说明：配置映射关系，使字段与DO类解耦，方便维护。</p>
</li>
<li><p><strong>【强制】</strong> sql.xml配置参数使用：#{}，#param#不要使用${}此种方式容易出现SQL注入。</p>
</li>
<li><p><strong>【强制】</strong> iBATIS自带的queryForList(String statementName,int start,int size)不推荐使用。<br>说明：其实现方式是在数据库取到statementName对应的SQL语句的所有记录，再通过subList取start,size的子集合。<br>正例：</p>
<pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"start"</span><span class="token punctuation">,</span> start<span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"size"</span><span class="token punctuation">,</span>size<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
</li>
<li><p><strong>【强制】</strong> 不允许直接拿HashMap与Hashtable作为查询结果集的输出。<br>反例：某同学为避免写一个&lt;resultMap&gt;，直接使用HashTable来接收数据库返回结果，结果出现日常是把bigint转成Long值，而线上由于数据库版本不一样，解析成BigInteger，导致线上问题。</p>
</li>
<li><p><strong>【强制】</strong> 更新数据表记录时，必须同时更新记录对应的gmt_modified字段值为当前时间。</p>
</li>
<li><p>【推荐】不要写一个大而全的数据更新接口。传入为POJO类，不管是不是自己的目标更新字段，都进行update table set c1 &#x3D;value1,c2&#x3D;value2,c3&#x3D;value3;这是不对的。执行SQL时，不要更新无改动的字段，一是易出错；二是效率低；三是增加binlog存储。</p>
</li>
<li><p>【参考】@Transactional事务不要滥用。事务会影响数据库的qps，另外使用事务的地方需要考虑各方面的回滚方案，包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。</p>
</li>
<li><p>【参考】&lt;isEqual&gt;中的compareValue是与属性值对比的常量，一般是数字，表示相等时带上此条件；&lt;isNotEmpty&gt;表示不为空且不为null时执行；&lt;isNotNull&gt;表示不为null值时执行。</p>
</li>
</ol>
<h2 id="六、工程结构"><a href="#六、工程结构" class="headerlink" title="六、工程结构"></a>六、工程结构</h2><h3 id="一-应用分层"><a href="#一-应用分层" class="headerlink" title="(一) 应用分层"></a>(一) 应用分层</h3><ol>
<li>【推荐】图中默认上层依赖于下层，箭头关系表示可直接依赖，如：开放接口层可以依赖于<br>Web层，也可以直接依赖于Service层，依此类推：</li>
</ol>
<ul>
<li>开放接口层：可直接封装Service方法暴露成RPC接口；通过Web封装成http接口；网关控制层等。</li>
<li>终端显示层：各个端的模板渲染并执行显示的层。当前主要是velocity渲染，JS渲染，JSP渲染，移动端展示等。</li>
<li>Web层：主要是对访问控制进行转发，各类基本参数校验，或者不复用的业务简单处理等。</li>
<li>Service层：相对具体的业务逻辑月服务层。</li>
<li>Manager层：通用业务处理层，它有如下特征：<br>1)对第三方平台封装的层，预处理返回结果及转化异常信息。<br>2)对Service层通用能力的下沉，如缓存方案、中间件通用处理。<br>3)与DAO层交互，对多个DAO的组合复用。</li>
<li>DAO层：数据访问层，与底层MySQL、Oracle、Hbase、OB等进行数据交互。</li>
<li>外部接口或第三方平台：包括其它部门RPC开放接口，基础平台，其它公司的HTTP接口。</li>
</ul>
<ol start="2">
<li><p>【参考】（分层异常处理规约）在DAO层，产生的异常类型有很多，无法用细粒度的异常进行catch，使用catch(Exception e)方式，并throw new DAOException(e)，不需要打印日志，因为日志在Manager&#x2F;Service层一定需要捕获并打印到日志文件中去如果同台服务器再打日志，浪费性能和存储。在Service层出现异常时，必须记录出错日志到磁盘，尽可能带上参数信息,相当于保护案发现场。Manager层与Service同机部署，日志方式与DAO层处理一致，如果是单独部署，则采用与Service—致的处理方式。Web层绝不应该继续往上抛异常，因为已经处于顶层，如果意识到这个异常将导致页面无法正常渲染，那么就应该直接跳转到友好错误页面，尽量加上友好的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。</p>
</li>
<li><p>【参考】分层领域模型规约：</p>
</li>
</ol>
<ul>
<li>DO(Data Object):此对象与数据库表结构一一对应，通过DAO层向上传输数据源对象。</li>
<li>DTO(Data Transfer Object):数据传输对象，Service或Manager向外传输的对象。</li>
<li>BO(Business Object)：业务对象，可以由Service层输出的封装业务逻辑的对象。</li>
<li>Query :数据查询对象，各层接收上层的查询请求。注意超过2个参数的查询封装，禁止使用Map类来传输。</li>
<li>VO(View Object):显示层对象，通常是Web向模板渲染引擎层传输的对象。</li>
</ul>
<h3 id="二-二方库依赖"><a href="#二-二方库依赖" class="headerlink" title="(二) 二方库依赖"></a>(二) 二方库依赖</h3><ol>
<li><p><strong>【强制】</strong> 定义GAV遵从以下规则：<br>1)<strong>G</strong>roupID格式：com.{公司&#x2F;BU}.业务线[.子业务线]，最多4级。<br>说明：{公司&#x2F;BU}例如：alibaba&#x2F;taobao&#x2F;tmall&#x2F;aliexpress等BU一级；子业务线可选。<br>正例:com.taobao.jstorm或com.alibaba.dubbo.register<br>2)<strong>A</strong>rtifactID格式：产品线名-模块名。语义不重复不遗漏，先到中央仓库去查证一下。<br>正例：dubbo-client&#x2F;fastjson-api&#x2F;jstorm-tool<br>3)<strong>V</strong>ersion:详细规定参考下方。</p>
</li>
<li><p><strong>【强制】</strong> 二方库版本号命名方式：主版本号.次版本号.修订号<br>1)主版本号:产品方向改变，或者大规模API不兼容，或者架构不兼容升级。<br>2)次版本号：保持相对兼容性，增加主要功能特性，影响范围极小的API不兼容修改。<br>3)修订号:保持完全兼容性，修复BUG、新增次要功能特性等。<br>说明：注意起始版本号必须为：<strong>1.0.0</strong>，而不是0.0.1。<br>反例：仓库内某二方库版本号从1.0.0.0开始，一直默默”升级”成1.0.0.64，完全失去版本的语义信息。</p>
</li>
<li><p><strong>【强制】</strong> 线上应用不要依赖SNAPSHOT版本(安全包除外)；正式发布的类库必须先去中央仓库进行查证，使RELEASE版本号有延续性，且版本号不允许覆盖升级。<br>说明：不依赖SNAPSHOT版本是保证应用发布的幂等性。另外，也可以加快编译时的打包构建。</p>
</li>
<li><p><strong>【强制】</strong> 二方库的新增或升级，保持除功能点之外的其它jar包仲裁结果不变。如果有改变，必须明确评估和验证。<br>说明：在升级时，进行dependency:resolve前后信息比对，如果仲裁结果完全不一致，那么通过dependency:tree命令，找出差异点，进行&lt;exclude&gt;排除jar包。</p>
</li>
<li><p><strong>【强制】</strong> 二方库里可以定义枚举类型，参数可以使用枚举类型，但是接口返回值不允许使用枚举类型或者包含枚举类型的POJO对象。</p>
</li>
<li><p><strong>【强制】</strong> 依赖于一个二方库群时，必须定义一个统一的版本变量，避免版本号不一致。<br>说明：依赖springframework-core,-context,-beans，它们都是同一个版本，可以定义一个变量来保存版本：${spring.version}，定义依赖的时候，引用该版本。</p>
</li>
<li><p><strong>【强制】</strong> 禁止在子项目的pom依赖中出现相同的GroupId，相同的ArtifactId，但是不同的Version。<br>说明：在本地调试时会使用各子项目指定的版本号，但是合并成一个war，只能有一个版本号出现在最后的lib目录中。曾经出现过线下调试是正确的，发布到线上却出故障的先例。</p>
</li>
<li><p>【推荐】底层基础技术框架、核心数据管理平台、或近硬件端系统谨慎引入第三方实现。</p>
</li>
<li><p>【推荐】所有pom文件中的依赖声明放在&lt;dependencies&gt;语句块中，所有版本仲裁放在&lt;dependencyManagement&gt;语句块中。<br>说明：&lt;dependencyManagement&gt;里只是声明版本，并不实现引入，因此子项目需要显式的声明依赖，version和scope都B读取自父pom。而&lt;dependencies&gt;所有声明在主pom的&lt;dependencies&gt;里的依赖都会自动引入，并默认被所有的子项目继承。</p>
</li>
<li><p>【推荐】二方库不要有配置项，最低限度不要再增加配置项。</p>
</li>
<li><p>【推荐】不要使用不稳定的工具包或者Utils类。<br>说明：不稳定指的是提供方无法做到向下兼容，在编译阶段正常，但在运行时产生异常，因此，尽量使用业界稳定的二方工具包。</p>
</li>
<li><p>【参考】为避免应用二方库的依赖冲突问题，二方库发布者应当遵循以下原则：<br>1)<strong>精简可控原则</strong>。移除一切不必要的API和依赖，只包含Service API、必要的领域模型对象、Utils类、常量、枚举等。如果依赖其它二方库，尽量是provided引入，让二方库使用者去依赖具体版本号；无log具体实现，只依赖日志框架。<br>2)<strong>稳定可追溯原则</strong>。每个版本的变化应该被记录，二方库由谁维护，源码在哪里，都需要能方便查到。除非用户主动升级版本，否则公共二方库的行为不应该发生变化。</p>
</li>
</ol>
<h3 id="三-服务器"><a href="#三-服务器" class="headerlink" title="(三) 服务器"></a>(三) 服务器</h3><ol>
<li><p>【推荐】高并发服务器建议调小TCP协议的time_wait超时时间。<br>说明：操作系统默认240秒后，才会关闭处于time_wait状态的连接，在高并发访问下，服务器端会因为处于time_wait的连接数太多，可能无法建立新的连接，所以需要在服务器上调小此等待值。<br>正例：在linux服务器上请通过变更&#x2F;etc&#x2F;sysctl.conf文件去修改该缺省值（秒）：net.ipv4.tcp_fi n_ti meout &#x3D; 30</p>
</li>
<li><p>【推荐】调大服务器所支持的最大文件句柄数（File Descriptor，简写为fd）。<br>说明：主流操作系统的设计是将TCP&#x2F;UDP连接采用与文件一样的方式去管理，即一个连接对应于一个fd。主流的linux服务器默认所支持最大fd数量为1024当并发连接数很大时很容易因为fd不足而出现“open too many files”错误，导致新的连接无法建立。建议将linux服务器所支持的最大句柄数调高数倍（与服务器的内存数量相关）。</p>
</li>
<li><p>【推荐】给JVM环境参数设置-XX:+HeapDumpOnOutOfMemoryError参数，让JVM碰到OOM场景时输出dump信息。<br>说明：OOM的发生是有概率的，甚至相隔数月才出现一例，出错时的堆内信息对解决问题非常有帮助。</p>
</li>
<li><p>【推荐】在线上生产环境，JVM的Xms和Xmx设置一样大小的内存容量，避免在GC后调整堆大小带来的压力。</p>
</li>
<li><p>【参考】服务器内部重定向必须使用forward；外部重定向地址必须使用URL Broker生成，否则因线上采用HTTPS协议而导致浏览器提示”不安全“。此外，还会带来URL维护不一致的问题。</p>
</li>
</ol>
<h2 id="七、设计规约"><a href="#七、设计规约" class="headerlink" title="七、设计规约"></a>七、设计规约</h2><ol>
<li><p><strong>【强制】</strong>  <strong>存储方案</strong>和<strong>底层数据结构</strong>的设计获得评审一致通过，并沉淀成为文档。<br>说明：有缺陷的底层数据结构容易导致系统风险上升，可扩展性下降，重构成本也会因历史数据迁移和系统平滑过渡而陡然增加，所以，存储方案和数据结构需要认真地进行设计和评审，生产环境提交执行后，需要进行double check。<br>正例：评审内容包括存储介质选型、表结构设计能否满足技术方案、存取性能和存储空间能否满足业务发展、表或字段之间的辩证关系、字段名称、字段类型、索引等；数据结构变更（如在原有表中新增字段）也需要进行评审通过后上线。</p>
</li>
<li><p><strong>【强制】</strong> 在需求分析阶段，如果与系统交互的User超过<strong>一类</strong>并且相关的User Case超过<strong>5个</strong>，使用用例图来表达更加清晰的结构化需求。</p>
</li>
<li><p><strong>【强制】</strong> 如果某个业务对象的状态超过<strong>3个</strong>，使用状态图来表达并且明确状态变化的各个触发条件。<br>说明：状态图的核心是对象状态，首先明确对象有多少种状态，然后明确两两状态之间是否存在直接转换关系，再明确触发状态转换的条件是什么。<br>正例：淘宝订单状态有已下单、待付款、已付款、待发货、已发货、已收货等。比如已下单与已收货这两种状态之间是不可能有直接转换关系的。</p>
</li>
<li><p><strong>【强制】</strong> 如果系统中某个功能的调用链路上的涉及对象超过<strong>3个</strong>，使用时序图来表达并且明确各调用环节的输入与输出。<br>说明：时序图反映了一系列对象间的交互与协作关系，清晰立体地反映系统的调用纵深链路。</p>
</li>
<li><p><strong>【强制】</strong> 如果系统中模型类超过<strong>5个</strong>，并且存在复杂的依赖关系，使用类图来表达并且明确类之间的关系。<br>说明：类图像建筑领域的施工图，如果搭平房，可能不需要，但如果建造蚂蚁Z空间大楼，肯定需要详细的施工图。</p>
</li>
<li><p><strong>【强制】</strong> 如果系统中超过<strong>2个</strong>对象之间存在协作关系，并且需要表示复杂的处理流程，使用活动图来表示。<br>说明：活动图是流程图的扩展，增加了能够体现协作关系的对象泳道，支持表示并发等。</p>
</li>
<li><p>【推荐】系统架构设计时明确以下目标：</p>
</li>
</ol>
<ul>
<li>确定系统边界。确定系统在技术层面上的做与不做。</li>
<li>确定系统内模块之间的关系。确定模块之间的依赖关系及模块的宏观输入与输出。</li>
<li>确定指导后续设计与演化的原则。使后续的子系统或模块设计在一个既定的框架内和技术方向上继续演化。</li>
<li>确定非功能性需求。非功能性需求是指安全性、可用性、可扩展性等。</li>
</ul>
<ol start="8">
<li><p>【推荐】需求分析与系统设计在考虑主干功能的同时，需要充分评估异常流程与业务边界。<br>反例：用户在淘宝付款过程中，银行扣款成功，发送给用户扣款成功短信，但是支付宝入款时由于断网演练产生异常，淘宝订单页面依然显示未付款，导致用户投诉。</p>
</li>
<li><p>【推荐】类在设计与实现时要符合单一原则。<br>说明：单一原则最易理解却是最难实现的一条规则，随着系统演进，很多时候，忘记了类设计的初衷。</p>
</li>
<li><p>【推荐】谨慎使用继承的方式来进行扩展，优先使用聚合&#x2F;组合的方式来实现。<br>说明：不得已使用继承的话，必须符合里氏代换原则，此原则说父类能够出现的地方子类一定能够出现，比如，”把钱交出来”，钱的子类美元、欧元、人民币等都可以出现。</p>
</li>
<li><p>【推荐】系统设计阶段，根据依赖倒置原则，尽量依赖抽象类与接口，有利于扩展与维护。<br>说明：低层次模块依赖于高层次模块的抽象，方便系统间的解耦。</p>
</li>
<li><p>【推荐】系统设计阶段，注意对扩展开放，对修改闭合。<br>说明：极端情况下，交付的代码是不可修改的，同一业务域内的需求变化，通过模块或类的扩展来实现。</p>
</li>
<li><p>【推荐】系统设计阶段，共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等，在系统中不出现重复代码的情况。<br>说明：随着代码的重复次数不断增加，维护成本指数级上升。</p>
</li>
<li><p>【推荐】避免如下误解：敏捷开发&#x3D;讲故事+编码+发布。<br>说明：敏捷开发是快速交付迭代可用的系统，省略多余的设计方案，摒弃传统的审批流程，但核心关键点上的必要设计和文档沉淀是需要的。<br>反例：某团队为了业务快速发展，敏捷成了产品经理催进度的借口，系统中均是勉强能运行但像面条一样的代码，可维护性和可扩展性极差，一年之后，不得不进行大规模重构，得不偿失。</p>
</li>
<li><p>【参考】设计文档的作用是明确需求、理顺逻辑、后期维护，次要目的用于指导编码。<br>说明：避免为了设计而设计，系统设计文档有助于后期的系统维护和重构，所以设计结果需要进行分类归档保存。</p>
</li>
<li><p>【参考】可扩展性的本质是找到系统的变化点，并隔离变化点。<br>说明：世间众多设计模式其实就是一种设计模式即隔离变化点的模式。<br>正例：极致扩展性的标志，就是需求的新增，不会在原有代码交付物上进行任何形式的修改。</p>
</li>
<li><p>【参考】设计的本质就是识别和表达系统难点。<br>说明：识别和表达完全是两回事，很多人错误地认为识别到系统难点在哪里，表达只是自然而然的事情，但是大家在设计评审中经常出现语焉不详，甚至是词不达意的情况。准确地表达系统难点需要具备如下能力：表达规则和表达工具的熟练性。抽象思维和总结能力的局限性。基础知识体系的完备性。深入浅出的生动表达力。</p>
</li>
<li><p>【参考】代码即文档的观点是错误的，清晰的代码只是文档的某个片断，而不是全部。<br>说明：代码的深度调用，模块层面上的依赖关系网，业务场景逻辑，非功能性需求等问题是需要相应的文档来完整地呈现的。</p>
</li>
<li><p>【参考】在做无障碍产品设计时，需要考虑到：</p>
</li>
</ol>
<ul>
<li>所有可交互的控件元素必须能被tab键聚焦，并且焦点）1顺序需符合自然操作逻辑。</li>
<li>用于登陆校验和请求拦截的验证码均需提供图形验证以外的其它方式。</li>
<li>自定义的控件类型需明确交互方式。<br>正例：用户登陆场景中，输入框的按钮都需要考虑tab键聚焦，符合自然逻辑的操作顺序如下，”输入用户名，输入密码，输入验证码，点击登录”，其中验证码实现语音验证方式。如果有自定义标签实现的控件设置控件类型可使用role属性。</li>
</ul>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/blog/about" rel="external nofollow noreferrer">xmxe</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://xmxe.github.io/blog/posts/d791cb4f9835/">https://xmxe.github.io/blog/posts/d791cb4f9835/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/blog/about" target="_blank">xmxe</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            <span class="chip bg-color">无标签</span>
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/blog/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="qq,qzone,wechat,weibo,douban" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/blog/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        
                            <img src="/blog/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                        
                    </div>
                    <div id="wechat">
                        
                            <img src="/blog/medias/reward/wechat.jpg" class="reward-img" alt="微信打赏二维码">
                        
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/blog/posts/b435885d7cf1/">
                    <div class="card-image">
                        
                        <img src="https://picx.zhimg.com/v2-5668c094310c3d27b25ea3df98b9e43c_1440w.jpg" class="responsive-img" alt="Spring中的Bean对象">
                        
                        <span class="card-title">Spring中的Bean对象</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            Spring Bean、生命周期、扩展接口
                        

                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <!--
                            <i class="far fa-clock fa-fw icon-date"></i>2023-03-03
                            -->

                            
                                <i class="fas fa-user fa-fw"></i>
                                <a href="/blog/about" >
                                    xmxe
                                </a>
                            
                        </span>
                        <span class="publish-author">
                            
                                <i class="fas fa-bookmark fa-fw icon-category"></i>
                                
                                    <a href="/blog/categories/Spring/" class="post-category">
                                        Spring
                                    </a>
                                
                            

                        </span>
                    </div>
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/blog/posts/56b099421a97/">
                    <div class="card-image">
                        
                        <img src="https://picd.zhimg.com/v2-f9c4c6b8a4b0f096032990926e1f0a1a_1440w.jpg" class="responsive-img" alt="Spring Security介绍">
                        
                        <span class="card-title">Spring Security介绍</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
github地址

HttpSecurity常用方法


HttpSecurity常用方法
说明



openidLogin()
用于基于OpenId的验证


headers()
将安全标头添加到响应


cors()
配置跨域资源共
                        
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <!--
                            <i class="far fa-clock fa-fw icon-date"></i>2022-11-16
                            -->
                            
                                <i class="fas fa-user fa-fw"></i>
                                <a href="/blog/about" >
                                    xmxe
                                </a>
                            
                        </span>
                        <span class="publish-author">
                            
                                <i class="fas fa-bookmark fa-fw icon-category"></i>
                                
                                    <a href="/blog/categories/Spring/" class="post-category">
                                        Spring
                                    </a>
                                
                            
                        </span>
                    </div>
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/blog/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/blog/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/blog/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/blog/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/blog/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h1, h2, h3, h4, h5, h6'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h1, h2, h3, h4, h5, h6').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    
        <link rel="stylesheet" href="/blog/libs/aplayer/APlayer.min.css">
<style>
    .aplayer .aplayer-lrc p {
        
        display: none;
        
        font-size: 12px;
        font-weight: 700;
        line-height: 16px !important;
    }

    .aplayer .aplayer-lrc p.aplayer-lrc-current {
        
        display: none;
        
        font-size: 15px;
        color: #42b983;
    }

    
    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
        left: -66px !important;
    }

    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
        left: 0px !important;
    }

    
</style>
<div class="">
    
    <div class="row">
        <meting-js class="col l8 offset-l2 m10 offset-m1 s12"
                   server="netease"
                   type="song"
                   id="569200213"
                   fixed='true'
                   autoplay='false'
                   theme='#42b983'
                   loop='all'
                   order='random'
                   preload='auto'
                   volume='0.7'
                   list-folded='true'
        >
        </meting-js>
    </div>
</div>

<script src="/blog/libs/aplayer/APlayer.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script> -->

    
    <div class="container row center-align" style="margin-bottom: 0px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
            
                <span id="year">2022-2025
                </span>
            
            

            <a href="/blog/about" target="_blank">
                xmxe
            </a>
            |&nbsp;Powered by&nbsp;
            <a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;
            <a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>

            
            
            
            
            
            

            
            <br>

            
            <br>

            
        </div>
        <div class="col s12 m4 l4 social-link2 ">
    <a href="https://github.com/xmxe" class="tooltipped" target="_blank" data-tooltip="GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="https://gitee.com/xmxe" class="tooltipped" target="_blank" data-tooltip="码云" data-position="top" data-delay="50">
        <svg width="19" height="19" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" style="position: relative; top: 2px; left: 0.5px;">
            <path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m259.2-569.6H480c-12.8 0-25.6 12.8-25.6 25.6v64c0 12.8 12.8 25.6 25.6 25.6h176c12.8 0 25.6 12.8 25.6 25.6v12.8c0 41.6-35.2 76.8-76.8 76.8h-240c-12.8 0-25.6-12.8-25.6-25.6V416c0-41.6 35.2-76.8 76.8-76.8h355.2c12.8 0 25.6-12.8 25.6-25.6v-64c0-12.8-12.8-25.6-25.6-25.6H416c-105.6 0-188.8 86.4-188.8 188.8V768c0 12.8 12.8 25.6 25.6 25.6h374.4c92.8 0 169.6-76.8 169.6-169.6v-144c0-12.8-12.8-25.6-25.6-25.6z" fill="#fff">
            </path>
        </svg>
    </a>

















    
        
          <a href="/blog/download" class="tooltipped" target="_blank" data-tooltip="下载" data-position="top" data-delay="50">
            <i class="fas fa-download"></i>
          </a>
        
    



    <style>
  .mobiledevice {
    display: none !important;
  }

  footer .wechat_qrcode {
    position: fixed;
  }

  /*微信二维码*/
  .wechat_qrcode {
    position: absolute;
    margin-left: 10px;
    bottom: 10px;
    background: url("/blog/medias/xcx.png");
    zoom:40%;
  }

  .wechat:hover .wechat_qrcode {
    width: 430px;
    height: 430px;
    animation: move 0.4s linear 1 normal;
  }

  @keyframes move {
    0% {
      transform: translate(100px, 0);
      opacity: 0;
    }
    50% {
      transform: translate(50px, 0);
      opacity: 0.5;
    }
    100% {
      transform: translate(0, 0);
      opacity: 1;
    }
  }

  @media only screen and (max-width: 601px) {
    .wechat {
      display: none !important;
    }
    .mobiledevice {
      display: inline-block !important;
    }
  }
</style>

<a href="javascript:;" class="wechat" data-position="top" data-delay="50">
  <i class="fab fa-weixin"></i>
  <img class="wechat_qrcode" />
</a>

<a
  href="javascript:;"
  class="tooltipped mobiledevice"
  data-tooltip="微信: 464817304"
  data-position="top"
  data-delay="50"
>
  <i class="fab fa-weixin"></i>
</a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/blog/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>

    <div class="stars-con">
  <div id="stars"></div>
  <div id="stars2"></div>
  <div id="stars3"></div>  
</div>

<!-- 白天和黑夜主题 -->



<script>
  function switchNightMode() {
    
      setTimeout(function () {
        $('body').hasClass('DarkMode') 
        ? ($('body').removeClass('DarkMode'), localStorage.setItem('isDark', '0'), $('#sum-moon-icon').removeClass("fa-sun").addClass('fa-moon')) 
        : ($('body').addClass('DarkMode'), localStorage.setItem('isDark', '1'), $('#sum-moon-icon').addClass("fa-sun").removeClass('fa-moon')),
          
        setTimeout(function () {
          $('.Cuteen_DarkSky').fadeOut(1e3, function () {
            $(this).remove()
          })
        }, 2e3)
      })
  }
</script>
    
    
    <script>
        /* 模式判断 */
        if (localStorage.getItem('isDark') === '1') {
            document.body.classList.add('DarkMode');
            $('#sum-moon-icon').addClass("fa-sun").removeClass('fa-moon')
        } else {
            document.body.classList.remove('DarkMode');
            $('#sum-moon-icon').removeClass("fa-sun").addClass('fa-moon')
        }
    </script>

    <script src="/blog/libs/materialize/materialize.min.js"></script>
    <script src="/blog/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/blog/libs/aos/aos.js"></script>
    <script src="/blog/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/blog/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/blog/js/matery.js"></script>

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    

    

    

	
    

    

    
    <script type="text/javascript" src="/blog/libs/background/ribbon-dynamic.js" async="async"></script>
    

    

    <!-- 冒泡 -->
    
    <script type="text/javascript">
        // 只在桌面版网页启用特效
        // var windowWidth = $(window).width();
        
            document.write('<script type="text/javascript" src="/blog/libs/others/bubleAll.js"><\/script>');
        
        
    </script>
    

    <!-- 弹出文字 -->
    

</body>

</html>
